Implement led per page

This commit is contained in:
2026-05-19 13:09:07 +02:00
parent 79a3cbe66e
commit 9be0b12085
7 changed files with 332 additions and 13 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "basic"
}
+11 -6
View File
@@ -21,15 +21,20 @@ The configuration (handmade) on which this generator is based, can be found in t
## Todo
### Ideas
- SHow image service
- LED effects: flash, breathe
- Show image service
- Top level config only for drawing, everything else hidden
- back to using packages or includes probably
- Allow adding of font and icons easily
- LED set per page!
- with --no-split option
- two files
### Missing config
- Event lambda's (see config.yaml)
- Implement --no-click-confirm
- Fix LED effects: Blink and Breathe
- And make effects optional (--no-effects)
- Make effects optional (--no-effects)
- service: get led state
- led name
- page name
- page num
- service: get_page_info
- rename the service
- add the effect options
+6 -1
View File
@@ -16,6 +16,7 @@ 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.services import config_services
from buttonplus_generator.snippets.leds import config_leds
from buttonplus_generator.merger import deep_merge
from buttonplus_generator.utils import list_to_click_type
@@ -62,9 +63,11 @@ def compose_config(
),
)
config = deep_merge(config, config_leds(version=version, num_bars=num_bars, page_names=pages))
return config
def yaml_dumper(config: Dict[str, Any], fh) -> str:
def yaml_dumper(config: Dict[str, Any], fh):
## Handle multiline strings
def represent_multistr(dumper, data):
@@ -79,6 +82,8 @@ def yaml_dumper(config: Dict[str, Any], fh) -> str:
return dumper.represent_scalar("!extend", data.value)
def represent_lambda(dumper, data):
if len(data.value.splitlines()) > 1: # check for multiline string
return dumper.represent_scalar("!lambda", data.value, style="|")
return dumper.represent_scalar("!lambda", data.value, style='"')
def represent_secret(dumper, data):
+1 -1
View File
@@ -431,7 +431,7 @@ def bars_draw_scripts(num_bars: int, page_names: List[str]) -> Dict[str, Any]:
update_list.extend(_bars_script_execute_entry(3, Side.LEFT))
update_list.extend(_bars_script_execute_entry(3, Side.RIGHT))
bar_dict["script"].append({"id": "update_bars", "then": update_list[:-1]})
bar_dict["script"].append({"id": bar_id(IdSuffix.SCRIPT_UPDATE, None), "then": update_list[:-1]})
return bar_dict
+3 -3
View File
@@ -1,6 +1,6 @@
from typing import Dict, Any, List
from buttonplus_generator.merger import deep_merge
from buttonplus_generator.utils import slugify, array_to_cpp_vector
from buttonplus_generator.utils import bar_id, display_id, slugify, array_to_cpp_vector
from buttonplus_generator.constants import *
@@ -397,7 +397,7 @@ def update_scripts(num_bars: int) -> Dict[str, Any]:
update_list = [
{
"script.execute": "update_bars",
"script.execute": bar_id(IdSuffix.SCRIPT_UPDATE, None),
},
{"delay": "0.1s"},
]
@@ -406,7 +406,7 @@ def update_scripts(num_bars: int) -> Dict[str, Any]:
update_list.extend(
[
{
"script.execute": "update_main",
"script.execute": display_id(IdSuffix.SCRIPT_UPDATE),
},
{"delay": "0.1s"},
]
+305
View File
@@ -0,0 +1,305 @@
from typing import Dict, Any, List
from buttonplus_generator.constants import *
from buttonplus_generator.merger import deep_merge
from buttonplus_generator.utils import (
array_to_cpp_vector,
array_to_cpp_vector_raw,
display_id,
bar_id,
)
def config_leds(version: int, num_bars: int, page_names: List[str]) -> Dict[str, Any]:
config = leds_globals(version=version, num_bars=num_bars, page_names=page_names)
config = deep_merge(config, leds_actions(version=version, num_bars=num_bars))
config = deep_merge(config, leds_script(version=version, num_bars=num_bars))
return config
def leds_actions(version: int, num_bars: int) -> Dict[str, Any]:
all_led_ids = _get_list_leds(version, num_bars)
generated_cpp = ""
#TODO add effect to page info, or not if --no-effects
for led_id in all_led_ids:
generated_cpp += f'leds.add("{led_id}");\n'
return {
"api": {
"actions": [
{
"action": "set_page_led",
"variables": {
"led": "string",
"page_name": "string",
"r": "float",
"g": "float",
"b": "float",
"brightness": "float",
"effect": "string",
},
"then": [
{
"if": {
"condition": {
"lambda": (
"// Validate page_name if provided\n"
"auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name);\n"
"if (it == id(page_names).end()) return true;\n"
"return false;"
)
},
"then": [
{
"api.respond": {
"success": False,
"error_message": "Invalid page_name (not found).",
}
}
],
"else": [
{
"if": {
"condition": {
"lambda": "return id(internal_page_leds_r).find(led) == id(internal_page_leds_r).end();"
},
"then": [
{
"api.respond": {
"success": False,
"error_message": "The requested LED does not exist.",
}
}
],
"else": [
{
"lambda": (
"// Resolve final page index\n"
"auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name);\n"
"int page_num = std::distance(id(page_names).begin(), it);\n"
"\n"
"id(internal_page_leds_r)[led][page_num] = r;\n"
"id(internal_page_leds_g)[led][page_num] = g;\n"
"id(internal_page_leds_b)[led][page_num] = b;\n"
"id(internal_page_leds_brightness)[led][page_num] = brightness;\n"
"id(internal_page_leds_effect)[led][page_num] = effect;"
)
},
{"api.respond": {"success": True}},
{
"if": {
"condition": {
"lambda": (
"auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name);\n"
"int page_num = std::distance(id(page_names).begin(), it);\n"
"return page_num == id(active_page_nr);"
)
},
"then": [
{
"script.execute": "update_all_page_leds"
}
],
}
},
],
}
}
],
}
}
],
},
{
"action": "get_page_info",
"supports_response": "only",
"then": [
{
"api.respond": {
"data": YAMLLambda(
(
'root["num_pages"] = id(nr_of_pages);\n'
'JsonArray pages = root["page_names"].to<JsonArray>();\n'
"for(const std::string& page_name : id(page_names)){\n"
" pages.add(page_name);\n"
"}\n"
'JsonArray leds = root["leds"].to<JsonArray>();\n'
)
+ generated_cpp
)
}
}
],
},
]
}
}
def leds_script(version: int, num_bars: int) -> Dict[str, Any]:
all_led_ids = _get_list_leds(version, num_bars)
generated_cpp = ""
for led_id in all_led_ids:
generated_cpp += f'id(update_single_led).execute("{led_id}", id({led_id}), id(active_page_nr));\n'
return {
"script": [
{
"id": "update_all_page_leds",
"then": [{"lambda": generated_cpp}],
},
{
"id": "update_single_led",
"parameters": {
"led_name": "string",
"led_id": "light::LightState*",
"page": "int",
},
"then": [
{
"lambda": (
"if(led_id != nullptr){\n"
" float r = id(internal_page_leds_r)[led_name][page];\n"
" float g = id(internal_page_leds_g)[led_name][page];\n"
" float b = id(internal_page_leds_b)[led_name][page];\n"
" float br = id(internal_page_leds_brightness)[led_name][page];\n"
" std::string effect = id(internal_page_leds_effect)[led_name][page];\n"
" if(r > 1.5) r = r / 255.0;\n"
" if(g > 1.5) g = g / 255.0;\n"
" if(b > 1.5) b = b / 255.0;\n"
" if(br > 1.5) br = br / 255.0;\n"
" auto call = led_id->make_call();\n"
" call.set_transition_length(10);\n"
" if(br <= 0.0001) {\n"
" call.set_state(false);\n"
" } else {\n"
" call.set_state(true);\n"
" call.set_rgb(r,g,b);\n"
" call.set_brightness(br);\n"
" call.set_effect(effect);\n"
" }\n"
" call.perform();\n"
"}"
)
}
],
},
{
"id": "set_page",
"then": [
{"script.execute": "update_all_page_leds"},
],
},
]
}
def leds_globals(version: int, num_bars: int, page_names: List[str]) -> Dict[str, Any]:
all_led_ids = _get_list_leds(version, num_bars)
return {
"globals": [
{
"id": "internal_page_leds_r",
"type": "std::unordered_map<std::string, std::vector<float>>",
"initial_value": _global_initialization_float(
all_led_ids, len(page_names)
),
},
{
"id": "internal_page_leds_g",
"type": "std::unordered_map<std::string, std::vector<float>>",
"initial_value": _global_initialization_float(
all_led_ids, len(page_names)
),
},
{
"id": "internal_page_leds_b",
"type": "std::unordered_map<std::string, std::vector<float>>",
"initial_value": _global_initialization_float(
all_led_ids, len(page_names)
),
},
{
"id": "internal_page_leds_brightness",
"type": "std::unordered_map<std::string, std::vector<float>>",
"initial_value": _global_initialization_float(
all_led_ids, len(page_names)
),
},
{
"id": "internal_page_leds_effect",
"type": "std::unordered_map<std::string, std::vector<std::string>>",
"initial_value": _global_initialization_string(
all_led_ids, len(page_names)
),
},
]
}
def _global_initialization_float(led_ids: List[str], num_pages: int) -> str:
zero_floats = ["0.0" for _ in range(num_pages)]
return _global_initialization(led_ids, array_to_cpp_vector_raw(zero_floats))
def _global_initialization_string(led_ids: List[str], num_pages: int) -> str:
zero_strings = ["" for _ in range(num_pages)]
return _global_initialization(led_ids, array_to_cpp_vector(zero_strings))
def _global_initialization(led_ids: List[str], init_list: str) -> str:
"""
{{"led_id_1", {0.0, 0.0}}, {"led_id_2", {0.0, 0.0}}};
"""
init_str = "{"
for led in led_ids:
init_str += '{"' + led + '", ' + init_list + "}, "
init_str = init_str[:-2] + "}"
return init_str
def _get_list_leds(version: int, num_bars: int) -> List[str]:
led_list: List[str] = []
if version == 2:
led_list.append(display_id(IdSuffix.LED, Side.LEFT, Direction.FRONT))
led_list.append(display_id(IdSuffix.LED, Side.RIGHT, Direction.FRONT))
led_list.append(display_id(IdSuffix.LED, Side.LEFT, Direction.BACK))
led_list.append(display_id(IdSuffix.LED, Side.RIGHT, Direction.BACK))
if num_bars > 0:
led_list.append(bar_id(IdSuffix.LED, 1, Side.LEFT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 1, Side.RIGHT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 1, Side.LEFT, Direction.BACK))
led_list.append(bar_id(IdSuffix.LED, 1, Side.RIGHT, Direction.BACK))
if num_bars > 1:
led_list.append(bar_id(IdSuffix.LED, 2, Side.LEFT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 2, Side.RIGHT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 2, Side.LEFT, Direction.BACK))
led_list.append(bar_id(IdSuffix.LED, 2, Side.RIGHT, Direction.BACK))
if num_bars > 2:
led_list.append(bar_id(IdSuffix.LED, 3, Side.LEFT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 3, Side.RIGHT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 3, Side.LEFT, Direction.BACK))
led_list.append(bar_id(IdSuffix.LED, 3, Side.RIGHT, Direction.BACK))
if num_bars > 3:
led_list.append(bar_id(IdSuffix.LED, 3, Side.LEFT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 3, Side.RIGHT, Direction.FRONT))
led_list.append(bar_id(IdSuffix.LED, 3, Side.LEFT, Direction.BACK))
led_list.append(bar_id(IdSuffix.LED, 3, Side.RIGHT, Direction.BACK))
return led_list
@@ -1,3 +1,4 @@
import copy
from typing import Dict, Any, List
from buttonplus_generator.merger import deep_merge
from buttonplus_generator.constants import *
@@ -184,8 +185,8 @@ def notification_service(version: int) -> Dict[str, Any]:
service_dict["script"].extend(
[
{"id": "next_page", "then": [dismiss_notification]},
{"id": "prev_page", "then": [dismiss_notification]},
{"id": "next_page", "then": [copy.deepcopy(dismiss_notification)]},
{"id": "prev_page", "then": [copy.deepcopy(dismiss_notification)]},
]
)