Implement button events
This commit is contained in:
@@ -20,7 +20,5 @@ The configuration (handmade) on which this generator is based, can be found in t
|
||||
- Allow adding of font and icons easily
|
||||
|
||||
## Missing config
|
||||
- Event generation (triggers and stuff, see events.yaml)
|
||||
- configurable timing and button events
|
||||
- Always set display to left and right page
|
||||
- Event lambda's (see config.yaml)
|
||||
- Allow turn off click-confirm
|
||||
@@ -18,11 +18,60 @@ class BarSide(StrEnum):
|
||||
LEFT = "left"
|
||||
RIGHT = "right"
|
||||
|
||||
class ClickTypes(StrEnum):
|
||||
SINGLE = "single"
|
||||
DOUBLE = "double"
|
||||
TRIPLE = "triple"
|
||||
class ClickType(StrEnum):
|
||||
SINGLE = "click"
|
||||
DOUBLE = "double_click"
|
||||
TRIPLE = "triple_click"
|
||||
HOLD = "hold"
|
||||
|
||||
class Color():
|
||||
|
||||
def __init__(self, hex: str):
|
||||
self.hex = hex.lstrip("#")
|
||||
|
||||
offset = 0
|
||||
|
||||
if(len(self.hex) == 6):
|
||||
offset = 2
|
||||
elif(len(self.hex) == 3):
|
||||
offset = 1
|
||||
else:
|
||||
raise ValueError("HEX must be either 3 or 6 characters long")
|
||||
|
||||
self.r = int(self.hex[0:offset], 16)
|
||||
self.g = int(self.hex[(offset):(offset*2)], 16)
|
||||
self.b = int(self.hex[(offset*2):(offset*3)], 16)
|
||||
|
||||
def hex(self) -> str:
|
||||
return "#" + self.hex
|
||||
|
||||
|
||||
|
||||
TIMING_SINGLE_CLICK = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s"
|
||||
]
|
||||
|
||||
TIMING_DOUBLE_CLICK = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s"
|
||||
]
|
||||
|
||||
TIMING_TRIPLE_CLICK = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s"
|
||||
]
|
||||
|
||||
TIMING_HOLD_CLICK = [
|
||||
"ON for at least 1s",
|
||||
"OFF for at least 0.2s"
|
||||
]
|
||||
|
||||
class YAMLExtend:
|
||||
def __init__(self, value): self.value = value
|
||||
|
||||
@@ -10,22 +10,23 @@ from typing import Any, Dict, List
|
||||
|
||||
import yaml
|
||||
|
||||
from buttonplus_generator.constants import YAMLExtend, YAMLLambda, YAMLSecret
|
||||
from buttonplus_generator.constants import YAMLExtend, YAMLLambda, YAMLSecret, ClickType
|
||||
from buttonplus_generator.snippets.common import config_common
|
||||
from buttonplus_generator.snippets.bars import config_bars
|
||||
from buttonplus_generator.snippets.display import config_display
|
||||
#from buttonplus_generator.snippets.events import config_events
|
||||
from buttonplus_generator.snippets.events import config_events
|
||||
from buttonplus_generator.merger import deep_merge
|
||||
from buttonplus_generator.utils import list_to_click_type
|
||||
|
||||
|
||||
def compose_config(device_name: str, version: int, num_bars: int, pages: List[str], click_types: List[str]) -> Dict[str, Any]:
|
||||
def compose_config(device_name: str, version: int, num_bars: int, pages: List[str], click_types: List[ClickType], click_confirm: bool) -> Dict[str, Any]:
|
||||
config = config_common(device_name=device_name, version=version, page_names=pages, num_bars=num_bars)
|
||||
|
||||
if(num_bars < 4): # Always assume display if less than 4 bars
|
||||
config = deep_merge(config, config_display(version=version, num_bars=num_bars, page_names=pages))
|
||||
|
||||
config = deep_merge(config, config_bars(version=version, num_bars=num_bars, page_names=pages))
|
||||
#config = deep_merge(config, config_events(version=version, num_bars=num_bars, page_names=pages, click_types=click_types))
|
||||
config = deep_merge(config, config_events(version=version, num_bars=num_bars, page_names=pages, click_types=click_types, click_confirm=click_confirm))
|
||||
|
||||
return config
|
||||
|
||||
@@ -47,14 +48,18 @@ def yaml_dumper(config: Dict[str, Any], fh) -> str:
|
||||
return dumper.represent_scalar('!lambda', data.value, style='"')
|
||||
|
||||
def represent_secret(dumper, data):
|
||||
return dumper.represent_scalar('!secret', data.value, style="'")
|
||||
return dumper.represent_scalar('!secret', data.value, style='')
|
||||
|
||||
yaml.add_representer(str, represent_multistr)
|
||||
yaml.add_representer(YAMLExtend, represent_extend)
|
||||
yaml.add_representer(YAMLLambda, represent_lambda)
|
||||
yaml.add_representer(YAMLSecret, represent_secret)
|
||||
|
||||
yaml.dump(config, fh, sort_keys=False, encoding="utf-8", default_flow_style=False)
|
||||
class NoAliasDumper(yaml.Dumper):
|
||||
def ignore_aliases(self, data):
|
||||
return True
|
||||
|
||||
yaml.dump(config, fh, sort_keys=False, encoding="utf-8", default_flow_style=False, Dumper=NoAliasDumper)
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -100,6 +105,12 @@ def main(argv=None) -> int:
|
||||
help="List click types buttons, choices are [single|double|triple|hold].",
|
||||
metavar="TYPE"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-click-confirm",
|
||||
default=True,
|
||||
action="store_false",
|
||||
help="Dont' flash the LED as a confirmation of the click event."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", "-o", default="buttonplus.yaml", help="Output file path"
|
||||
)
|
||||
@@ -109,10 +120,11 @@ def main(argv=None) -> int:
|
||||
version: int = args.version
|
||||
num_bars: int = args.bars
|
||||
pages: List[str] = args.pages
|
||||
click_types: List[str] = args.click_types
|
||||
click_types: List[ClickType] = list_to_click_type(args.click_types)
|
||||
click_confirm: bool = args.click_confirm
|
||||
output: str = args.output
|
||||
|
||||
config = compose_config(device_name, version, num_bars, pages, click_types)
|
||||
config = compose_config(device_name, version, num_bars, pages, click_types, click_confirm)
|
||||
with open(output, "w", encoding="utf-8") as fh:
|
||||
fh.writelines(
|
||||
[
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -8,13 +8,84 @@ def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
result = dict(a)
|
||||
for k, v in b.items():
|
||||
# Key from b also in a
|
||||
if k in result:
|
||||
# a[k] and b[k] are both a dictionary: recursive deep_merge
|
||||
if isinstance(result[k], dict) and isinstance(v, dict):
|
||||
result[k] = deep_merge(result[k], v)
|
||||
# a[k] and b[k] are both a list: list_merge
|
||||
elif isinstance(result[k], list) and isinstance(v, list):
|
||||
result[k] = result[k] + v
|
||||
result[k] = list_merge(result[k], v)
|
||||
else:
|
||||
result[k] = v
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
return result
|
||||
|
||||
def list_merge(a: List[Any], b: List[Any]) -> List[Any]:
|
||||
"""
|
||||
Merge two list together based on id's. This is similar to !extend in YAML.
|
||||
|
||||
Steps:
|
||||
1. Check if both lists contain Dicts with an `id` key in them
|
||||
1a. Otherwise just return a + b (extend)
|
||||
2. For every entry in a:
|
||||
3. Store id in a list for later
|
||||
4. Find the same id in b, if not found add to result without merge
|
||||
5. Perform a deep_merge on the dictionary, add to result list
|
||||
6. Ensure there are no id's missing from b, by checking every entry agains the list from step 3
|
||||
7. Return result
|
||||
"""
|
||||
|
||||
# If either list is empty just concatenate
|
||||
if not a or not b:
|
||||
return list(a) + list(b)
|
||||
|
||||
# Detect whether both lists contain dicts with an 'id' key
|
||||
a_has_id = any(isinstance(x, dict) and "id" in x for x in a)
|
||||
b_has_id = any(isinstance(x, dict) and "id" in x for x in b)
|
||||
|
||||
# If there are no id-based dicts in either list, just extend
|
||||
if not (a_has_id and b_has_id):
|
||||
return list(a) + list(b)
|
||||
|
||||
result: List[Any] = []
|
||||
seen_ids = set()
|
||||
|
||||
# Merge entries from `a`, matching by `id` in `b` when present
|
||||
for item in a:
|
||||
if isinstance(item, dict) and "id" in item:
|
||||
id_val = item.get("id")
|
||||
# find matching entry in b
|
||||
match = None
|
||||
for bi in b:
|
||||
if isinstance(bi, dict) and bi.get("id") == id_val:
|
||||
match = bi
|
||||
break
|
||||
|
||||
if match is not None:
|
||||
# deep_merge returns a new dict
|
||||
merged = deep_merge(item, match)
|
||||
result.append(merged)
|
||||
else:
|
||||
result.append(item)
|
||||
|
||||
if id_val is not None:
|
||||
seen_ids.add(id_val)
|
||||
else:
|
||||
# Not an id'd dict, keep as-is
|
||||
result.append(item)
|
||||
|
||||
# Add remaining entries from b that were not merged or that don't have ids
|
||||
for item in b:
|
||||
if isinstance(item, dict) and "id" in item:
|
||||
id_val = item.get("id")
|
||||
if id_val is None:
|
||||
result.append(item)
|
||||
elif id_val not in seen_ids:
|
||||
result.append(item)
|
||||
else:
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
@@ -147,6 +147,7 @@ def _bar_button_entry(bar_nr: int, base_id: str) -> List[Dict[str, Any]]:
|
||||
"platform": "gpio",
|
||||
"id": f"bar{bar_nr}_btn_left",
|
||||
"name": f"BAR {bar_nr} button Left",
|
||||
"internal": True,
|
||||
"pin": {
|
||||
"mcp23xxx": base_id,
|
||||
"number": 6,
|
||||
@@ -158,6 +159,7 @@ def _bar_button_entry(bar_nr: int, base_id: str) -> List[Dict[str, Any]]:
|
||||
"platform": "gpio",
|
||||
"id": f"bar{bar_nr}_btn_right",
|
||||
"name": f"BAR {bar_nr} button Right",
|
||||
"internal": True,
|
||||
"pin": {
|
||||
"mcp23xxx": base_id,
|
||||
"number": 2,
|
||||
|
||||
@@ -88,6 +88,7 @@ def display_buttons(num_bars: int) -> Dict[str, Any]:
|
||||
"platform": "gpio",
|
||||
"id": "main_display_btn_left",
|
||||
"name": "Main Display button Left",
|
||||
"internal": True,
|
||||
"pin": {
|
||||
"mcp23xxx": get_display_id(num_bars),
|
||||
"number": 6,
|
||||
@@ -99,6 +100,7 @@ def display_buttons(num_bars: int) -> Dict[str, Any]:
|
||||
"platform": "gpio",
|
||||
"id": "main_display_btn_right",
|
||||
"name": "Main Display button Right",
|
||||
"internal": True,
|
||||
"pin": {
|
||||
"mcp23xxx": get_display_id(num_bars),
|
||||
"number": 2,
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from buttonplus_generator.constants import *
|
||||
from buttonplus_generator.merger import deep_merge
|
||||
from buttonplus_generator.utils import click_type_color, click_type_timing
|
||||
|
||||
|
||||
def config_events(
|
||||
version: int,
|
||||
num_bars: int,
|
||||
page_names: List[str],
|
||||
click_types: List[ClickType],
|
||||
click_confirm: bool,
|
||||
) -> Dict[str, Any]:
|
||||
config = events(num_bars=num_bars, page_names=page_names, click_types=click_types)
|
||||
config = deep_merge(config, events_timing(
|
||||
version=version,
|
||||
num_bars=num_bars,
|
||||
click_types=click_types,
|
||||
click_confirm=click_confirm,
|
||||
))
|
||||
|
||||
if click_confirm:
|
||||
config = deep_merge(config, event_confirm_script())
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def events(
|
||||
num_bars: int, page_names: List[str], click_types: List[ClickType]
|
||||
) -> Dict[str, Any]:
|
||||
event_dict = {
|
||||
"substitutions": {
|
||||
"button_events": _generate_event_names(page_names, click_types)
|
||||
}
|
||||
}
|
||||
|
||||
event_dict["event"] = []
|
||||
|
||||
if num_bars < 4:
|
||||
event_dict["event"].extend(_event_display_entry(BarSide.LEFT))
|
||||
event_dict["event"].extend(_event_display_entry(BarSide.RIGHT))
|
||||
|
||||
if num_bars > 0:
|
||||
event_dict["event"].extend(_event_bar_entry(1, BarSide.LEFT))
|
||||
event_dict["event"].extend(_event_bar_entry(1, BarSide.RIGHT))
|
||||
|
||||
if num_bars > 1:
|
||||
event_dict["event"].extend(_event_bar_entry(2, BarSide.LEFT))
|
||||
event_dict["event"].extend(_event_bar_entry(2, BarSide.RIGHT))
|
||||
|
||||
if num_bars > 2:
|
||||
event_dict["event"].extend(_event_bar_entry(3, BarSide.LEFT))
|
||||
event_dict["event"].extend(_event_bar_entry(3, BarSide.RIGHT))
|
||||
|
||||
if num_bars > 3:
|
||||
event_dict["event"].extend(_event_bar_entry(4, BarSide.LEFT))
|
||||
event_dict["event"].extend(_event_bar_entry(4, BarSide.RIGHT))
|
||||
|
||||
return event_dict
|
||||
|
||||
|
||||
def _event_display_entry(display_side: BarSide) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"platform": "template",
|
||||
"id": f"display_btn_{display_side}_event",
|
||||
"name": f"DISPLAY Button {str(display_side).capitalize()}",
|
||||
"device_class": "button",
|
||||
"event_types": r"${button_events}",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _event_bar_entry(bar_nr: int, bar_side: BarSide) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"platform": "template",
|
||||
"id": f"bar{bar_nr}_btn_{bar_side}_event",
|
||||
"name": f"BAR {bar_nr} Button {str(bar_side).capitalize()}",
|
||||
"device_class": "button",
|
||||
"event_types": r"${button_events}",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _generate_event_names(
|
||||
page_names: List[str], click_types: List[ClickType]
|
||||
) -> List[str]:
|
||||
return [
|
||||
f"{str(ct)} page {page_num}"
|
||||
for page_num in range(0, len(page_names))
|
||||
for ct in click_types
|
||||
]
|
||||
|
||||
|
||||
def event_confirm_script() -> Dict[str, Any]:
|
||||
generated_cpp = (
|
||||
"event_id->trigger(event_msg);\n"
|
||||
"\n"
|
||||
"if(led_id != nullptr){\n"
|
||||
" auto call = led_id->make_call();\n"
|
||||
" call.set_state(true);\n"
|
||||
" call.set_rgb(r, g, b);\n"
|
||||
" call.set_brightness(1.0);\n"
|
||||
" call.set_flash_length(200); // Returns to previous state automatically\n"
|
||||
" call.perform();\n"
|
||||
"}"
|
||||
)
|
||||
|
||||
return {
|
||||
"script": [
|
||||
{
|
||||
"id": "event_trigger_feedback",
|
||||
"parameters": {
|
||||
"event_id": "esphome::template_::TemplateEvent*",
|
||||
"event_msg": "std::string",
|
||||
"led_id": "light::LightState*",
|
||||
"r": "float",
|
||||
"g": "float",
|
||||
"b": "float",
|
||||
},
|
||||
"then": [{"lambda": generated_cpp}],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def events_timing(
|
||||
version: int, num_bars: int, click_types: List[ClickType], click_confirm: bool
|
||||
) -> Dict[str, Any]:
|
||||
event_dict = {"binary_sensor": []}
|
||||
|
||||
if num_bars < 4:
|
||||
event_dict["binary_sensor"].extend(
|
||||
_display_button_entry(version, BarSide.LEFT, click_types, click_confirm)
|
||||
)
|
||||
event_dict["binary_sensor"].extend(
|
||||
_display_button_entry(version, BarSide.RIGHT, click_types, click_confirm)
|
||||
)
|
||||
|
||||
if num_bars > 0:
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(1, BarSide.LEFT, click_types, click_confirm)
|
||||
)
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(1, BarSide.RIGHT, click_types, click_confirm)
|
||||
)
|
||||
|
||||
if num_bars > 1:
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(2, BarSide.LEFT, click_types, click_confirm)
|
||||
)
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(2, BarSide.RIGHT, click_types, click_confirm)
|
||||
)
|
||||
|
||||
if num_bars > 2:
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(3, BarSide.LEFT, click_types, click_confirm)
|
||||
)
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(3, BarSide.RIGHT, click_types, click_confirm)
|
||||
)
|
||||
|
||||
if num_bars > 3:
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(3, BarSide.LEFT, click_types, click_confirm)
|
||||
)
|
||||
event_dict["binary_sensor"].extend(
|
||||
_bar_button_entry(3, BarSide.RIGHT, click_types, click_confirm)
|
||||
)
|
||||
|
||||
return event_dict
|
||||
|
||||
|
||||
def _bar_button_entry(
|
||||
bar_nr: int, bar_side: BarSide, click_types: List[ClickType], click_confirm: bool
|
||||
) -> List[Dict[str, Any]]:
|
||||
multi_click_list = []
|
||||
|
||||
# TODO click_confirm implement
|
||||
|
||||
for ct in click_types:
|
||||
multi_click_list.extend(
|
||||
_timing_entry(
|
||||
event_id=f"bar{bar_nr}_btn_{bar_side}_event",
|
||||
led_id=f"bar{bar_nr}_{bar_side}_rgb_front",
|
||||
event_type=ct,
|
||||
timing=click_type_timing(ct),
|
||||
color=click_type_color(ct),
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": f"bar{bar_nr}_btn_{bar_side}",
|
||||
"filters": [{"delayed_off": "10ms"}],
|
||||
"on_multi_click": multi_click_list,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _display_button_entry(
|
||||
version: int,
|
||||
display_side: BarSide,
|
||||
click_types: List[ClickType],
|
||||
click_confirm: bool,
|
||||
) -> List[Dict[str, Any]]:
|
||||
multi_click_list = []
|
||||
|
||||
# TODO click_confirm implement
|
||||
|
||||
led_id = f"main_display_{display_side}_rgb_front"
|
||||
|
||||
if version == 1: # No leds
|
||||
led_id = YAMLLambda("return nullptr;")
|
||||
|
||||
# Add special page changer events
|
||||
script = "prev_page" if display_side == BarSide.LEFT else "next_page"
|
||||
multi_click_list.append(
|
||||
{
|
||||
"timing": click_type_timing(ClickType.SINGLE),
|
||||
"then": [{"script.execute": script}],
|
||||
}
|
||||
)
|
||||
|
||||
# The other clicks
|
||||
other_click_types: List[ClickType] = click_types[:]
|
||||
other_click_types.remove(ClickType.SINGLE)
|
||||
|
||||
for ct in other_click_types:
|
||||
multi_click_list.extend(
|
||||
_timing_entry(
|
||||
event_id=f"display_btn_{display_side}_event",
|
||||
led_id=led_id,
|
||||
event_type=ct,
|
||||
timing=click_type_timing(ct),
|
||||
color=click_type_color(ct),
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": f"main_display_btn_{display_side}",
|
||||
"filters": [{"delayed_off": "10ms"}],
|
||||
"on_multi_click": multi_click_list,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _timing_entry(
|
||||
event_id: str, led_id: str, event_type: str, timing: List[str], color: Color
|
||||
) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"timing": timing,
|
||||
"then": [
|
||||
{
|
||||
"script.execute": {
|
||||
"id": "event_trigger_feedback",
|
||||
"event_id": event_id,
|
||||
"led_id": led_id,
|
||||
"r": color.r,
|
||||
"g": color.g,
|
||||
"b": color.b,
|
||||
"event_msg": YAMLLambda(
|
||||
'return "'
|
||||
+ event_type
|
||||
+ ' page " + std::to_string(id(active_page_nr));'
|
||||
),
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def old():
|
||||
# 1. Define event lists for substitutions
|
||||
base_types = ["click", "double_click", "triple_click", "hold"]
|
||||
display_types = [] + (base_types[1:] if INCLUDE_DISPLAY_EXTRA else [])
|
||||
|
||||
display_events = []
|
||||
button_events = []
|
||||
for p in range(NR_OF_PAGES):
|
||||
display_events.extend([f"{t} page {p}" for t in display_types])
|
||||
button_events.extend([f"{t} page {p}" for t in base_types])
|
||||
|
||||
# 2. Event Entities
|
||||
buttons = [
|
||||
("DISPLAY Button Left", "display_btn_left_event", "${display_events}"),
|
||||
("DISPLAY Button Right", "display_btn_right_event", "${display_events}"),
|
||||
("BAR1 Button Left", "bar1_btn_left_event", "${button_events}"),
|
||||
("BAR1 Button Right", "bar1_btn_right_event", "${button_events}"),
|
||||
("BAR2 Button Left", "bar2_btn_left_event", "${button_events}"),
|
||||
("BAR2 Button Right", "bar2_btn_right_event", "${button_events}"),
|
||||
("BAR3 Button Left", "bar3_btn_left_event", "${button_events}"),
|
||||
("BAR3 Button Right", "bar3_btn_right_event", "${button_events}"),
|
||||
]
|
||||
for name, eid, types in buttons:
|
||||
data["event"].append(
|
||||
{
|
||||
"platform": "template",
|
||||
"name": name,
|
||||
"id": eid,
|
||||
"device_class": "button",
|
||||
"event_types": types,
|
||||
}
|
||||
)
|
||||
|
||||
# 3. Binary Sensor Extensions
|
||||
data["binary_sensor"] = []
|
||||
triggers = {
|
||||
"click": (0.0, 1.0, 0.0), # Green
|
||||
"double_click": (0.0, 0.0, 1.0), # Blue
|
||||
"triple_click": (1.0, 0.0, 1.0), # Purple
|
||||
"hold": (1.0, 0.5, 0.0), # Orange
|
||||
}
|
||||
|
||||
btn_configs = [
|
||||
("main_display_btn_left", "display_btn_left_event", None, "prev_page"),
|
||||
("main_display_btn_right", "display_btn_right_event", None, "next_page"),
|
||||
("bar1_btn_left", "bar1_btn_left_event", "bar1_left_rgb_front", None),
|
||||
("bar1_btn_right", "bar1_btn_right_event", "bar1_right_rgb_front", None),
|
||||
("bar2_btn_left", "bar2_btn_left_event", "bar2_left_rgb_front", None),
|
||||
("bar2_btn_right", "bar2_btn_right_event", "bar2_right_rgb_front", None),
|
||||
("bar3_btn_left", "bar3_btn_left_event", "bar3_left_rgb_front", None),
|
||||
("bar3_btn_right", "bar3_btn_right_event", "bar3_right_rgb_front", None),
|
||||
]
|
||||
|
||||
for btn_id, event_id, led_id, page_script in btn_configs:
|
||||
sensor = {
|
||||
"id": ESPHomeExtend(btn_id),
|
||||
"filters": [{"delayed_off": "10ms"}],
|
||||
"internal": True,
|
||||
"on_multi_click": [],
|
||||
}
|
||||
|
||||
is_display = "main_display" in btn_id
|
||||
for t_name, color in triggers.items():
|
||||
if is_display and t_name != "click" and not INCLUDE_DISPLAY_EXTRA:
|
||||
continue
|
||||
|
||||
multi_click = {"timing": []}
|
||||
if t_name == "click":
|
||||
multi_click["timing"] = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s",
|
||||
]
|
||||
elif t_name == "double_click":
|
||||
multi_click["timing"] = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s",
|
||||
]
|
||||
elif t_name == "triple_click":
|
||||
multi_click["timing"] = [
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at most 0.2s",
|
||||
"ON for at most 300ms",
|
||||
"OFF for at least 0.2s",
|
||||
]
|
||||
elif t_name == "hold":
|
||||
multi_click["timing"] = ["ON for at least 1s", "OFF for at least 0.2s"]
|
||||
|
||||
actions = []
|
||||
if t_name == "click" and page_script:
|
||||
actions.append({"script.execute": page_script})
|
||||
else:
|
||||
# The Feedback call with proper ESPHome formatting
|
||||
actions.append(
|
||||
{
|
||||
"script.execute": {
|
||||
"id": "trigger_with_feedback",
|
||||
"event_id": event_id,
|
||||
"event_msg": ESPHomeLambda(
|
||||
f'return "{t_name} page " + std::to_string(id(active_page_nr));'
|
||||
),
|
||||
"led_id": (
|
||||
led_id
|
||||
if (led_id and LED_CONFIRMATION)
|
||||
else ESPHomeLambda("return nullptr;")
|
||||
),
|
||||
"r": color[0],
|
||||
"g": color[1],
|
||||
"b": color[2],
|
||||
}
|
||||
}
|
||||
)
|
||||
multi_click["then"] = actions
|
||||
sensor["on_multi_click"].append(multi_click)
|
||||
data["binary_sensor"].append(sensor)
|
||||
|
||||
return data
|
||||
@@ -2,6 +2,8 @@
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
from buttonplus_generator.constants import *
|
||||
|
||||
|
||||
def slugify(input : str) -> str:
|
||||
s = input.lower()
|
||||
@@ -29,3 +31,47 @@ def get_display_address(num_bars : int) -> str:
|
||||
raise ValueError("Can't have a display with this number of bars")
|
||||
|
||||
return 0x23 - num_bars
|
||||
|
||||
def string_to_click_type(ct:str) -> ClickType:
|
||||
if ct == "single":
|
||||
return ClickType.SINGLE
|
||||
elif ct == "double":
|
||||
return ClickType.DOUBLE
|
||||
elif ct == "triple":
|
||||
return ClickType.TRIPLE
|
||||
elif ct == "hold":
|
||||
return ClickType.HOLD
|
||||
|
||||
def list_to_click_type(str_list:List[str]) -> List[ClickType]:
|
||||
ct_list: List[ClickType] = []
|
||||
|
||||
for ct in str_list:
|
||||
ct_list.append(string_to_click_type(ct))
|
||||
|
||||
return ct_list
|
||||
|
||||
def click_type_color(ct: ClickType) -> Color:
|
||||
|
||||
if(ct is ClickType.SINGLE):
|
||||
return Color("#00FF00")
|
||||
elif(ct is ClickType.DOUBLE):
|
||||
return Color("#0000FF")
|
||||
elif(ct is ClickType.TRIPLE):
|
||||
return Color("#FF0000")
|
||||
elif(ct is ClickType.HOLD):
|
||||
return Color("#FF00FF")
|
||||
|
||||
return Color("#FFFFFF")
|
||||
|
||||
def click_type_timing(ct: ClickType) -> List[str]:
|
||||
|
||||
if(ct is ClickType.SINGLE):
|
||||
return TIMING_SINGLE_CLICK
|
||||
elif(ct is ClickType.DOUBLE):
|
||||
return TIMING_DOUBLE_CLICK
|
||||
elif(ct is ClickType.TRIPLE):
|
||||
return TIMING_TRIPLE_CLICK
|
||||
elif(ct is ClickType.HOLD):
|
||||
return TIMING_HOLD_CLICK
|
||||
|
||||
return TIMING_SINGLE_CLICK
|
||||
Reference in New Issue
Block a user