diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..457f44d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic" +} \ No newline at end of file diff --git a/README.md b/README.md index 371632d..3bd3ea6 100644 --- a/README.md +++ b/README.md @@ -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) \ No newline at end of file +- 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 \ No newline at end of file diff --git a/src/buttonplus_generator/generator.py b/src/buttonplus_generator/generator.py index d2187d1..5794c0b 100644 --- a/src/buttonplus_generator/generator.py +++ b/src/buttonplus_generator/generator.py @@ -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 @@ -61,10 +62,12 @@ def compose_config( click_confirm=click_confirm, ), ) + + 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): diff --git a/src/buttonplus_generator/snippets/bars.py b/src/buttonplus_generator/snippets/bars.py index ad8b3ab..645a749 100644 --- a/src/buttonplus_generator/snippets/bars.py +++ b/src/buttonplus_generator/snippets/bars.py @@ -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 diff --git a/src/buttonplus_generator/snippets/common.py b/src/buttonplus_generator/snippets/common.py index 68ae390..75f31e3 100644 --- a/src/buttonplus_generator/snippets/common.py +++ b/src/buttonplus_generator/snippets/common.py @@ -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"}, ] diff --git a/src/buttonplus_generator/snippets/leds.py b/src/buttonplus_generator/snippets/leds.py new file mode 100644 index 0000000..6858b1a --- /dev/null +++ b/src/buttonplus_generator/snippets/leds.py @@ -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();\n' + "for(const std::string& page_name : id(page_names)){\n" + " pages.add(page_name);\n" + "}\n" + 'JsonArray leds = root["leds"].to();\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>", + "initial_value": _global_initialization_float( + all_led_ids, len(page_names) + ), + }, + { + "id": "internal_page_leds_g", + "type": "std::unordered_map>", + "initial_value": _global_initialization_float( + all_led_ids, len(page_names) + ), + }, + { + "id": "internal_page_leds_b", + "type": "std::unordered_map>", + "initial_value": _global_initialization_float( + all_led_ids, len(page_names) + ), + }, + { + "id": "internal_page_leds_brightness", + "type": "std::unordered_map>", + "initial_value": _global_initialization_float( + all_led_ids, len(page_names) + ), + }, + { + "id": "internal_page_leds_effect", + "type": "std::unordered_map>", + "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 diff --git a/src/buttonplus_generator/snippets/services.py b/src/buttonplus_generator/snippets/services.py index 180d5ac..4282ddc 100644 --- a/src/buttonplus_generator/snippets/services.py +++ b/src/buttonplus_generator/snippets/services.py @@ -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)]}, ] )