From 53ced30b6f4e9f6a0c984702ae80a7c3d443e29b Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 02:51:29 +0100 Subject: [PATCH 01/14] Explain new architecture in the README --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 759d20d..baf596c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ -# Packaging status: -## PIO +# Lib Ilo + [![PlatformIO Registry](https://badges.registry.platformio.org/packages/intuition-rt/library/ilo.svg)](https://registry.platformio.org/libraries/intuition-rt/ilo) -## PyPi -... +Ce projet contiens les différentes libraries nécessaire au bon fonctionnement de la communication avec ilo. + +Afin de simplifier le dévelopement, tout la partie communication est génération à partir d'une unique source de vérité. Cela permet d'assure que les different bindings sont synchronisé et utilise les mêmes trames. + +## Layout + +```c +artefacts/ # contenu généré automatiquement +├── primitives/ # abstraction simple des trames en fonctions +│ └── {cpp,python,flutter}/ +└── trames.json +bindings/ # wrapper au dessus des primitives +└── {cpp/python/flutter}/ +core/ # source de vérité des des données +generators/ + └── generate_primitives.py +``` From 577f938a1d673249f435b7babdb6ab4db149b5b4 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Sun, 30 Nov 2025 23:21:18 +0100 Subject: [PATCH 02/14] Generate a trames.json from src/trame.c --- .gitignore | 3 +++ Makefile | 8 ++++++++ generators/generate-trame-json.c | 19 +++++++++++++++++++ main.c | 10 ---------- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 generators/generate-trame-json.c delete mode 100644 main.c diff --git a/.gitignore b/.gitignore index 9bb57b1..fdc91c4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,8 @@ libilo.so .build compile_commands.json +# C binaries +trame-exporter + # PlatformIO .pio diff --git a/Makefile b/Makefile index 3e6c17f..5b706af 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,14 @@ install: $(NAME_release) uninstall: $(RM) $(BINDIR)/$(NAME_release) +trame-exporter: + $(LINK.c) -o $@ generators/generate-trame-json.c + +artefacts: + @ mkdir -p $@ + +artefacts/trames.json: trame-exporter artefacts + ./$< > $@ V ?= 0 ifneq ($(V),0) diff --git a/generators/generate-trame-json.c b/generators/generate-trame-json.c new file mode 100644 index 0000000..dfa6ab4 --- /dev/null +++ b/generators/generate-trame-json.c @@ -0,0 +1,19 @@ +#include + +// TODO: fixme +#include "../src/trame.c" + +int main(void) +{ + printf("[\n"); + for (size_t i = 0; i < TRAME_COUNT; i++) { + printf(" { "); + printf("\"name\": \"%s\", ", TRAMES[i].name); + printf("\"format\": \"%s\"", TRAMES[i].format); + if (i + 1 < TRAME_COUNT) + printf(" },\n"); + else + printf(" }\n"); + } + printf("]\n"); +} diff --git a/main.c b/main.c deleted file mode 100644 index 1fef439..0000000 --- a/main.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "src/trame.h" - -int main(void) -{ - char *p = build_simple_trame("get_motors_ilo_acc"); - - printf("[%s]\n", p); -} From 6aabec11bd1c7c131739d95119a28b5b18a64f8a Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 03:00:12 +0100 Subject: [PATCH 03/14] Make sure the artefact dir is read-only for git --- artefacts/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 artefacts/.gitignore diff --git a/artefacts/.gitignore b/artefacts/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/artefacts/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 1196e1c2195c894e549d99aa4e765f16b2f7369d Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 03:25:34 +0100 Subject: [PATCH 04/14] Setup jinja generator for primitives --- flake.nix | 2 +- generators/generate_primitives.py | 45 +++++++++++++++++++++ generators/templates/cpp/trames.cpp.jinja | 1 + generators/templates/python/trames.py.jinja | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 generators/generate_primitives.py create mode 100644 generators/templates/cpp/trames.cpp.jinja create mode 100644 generators/templates/python/trames.py.jinja diff --git a/flake.nix b/flake.nix index a4a8296..910549e 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ clang-tools scom compiledb - python3 + (python3.withPackages (p: [ p.jinja2 ])) ]; env.NIX_CFLAGS_COMPILE = diff --git a/generators/generate_primitives.py b/generators/generate_primitives.py new file mode 100644 index 0000000..1556efc --- /dev/null +++ b/generators/generate_primitives.py @@ -0,0 +1,45 @@ +import json +from pathlib import Path +from jinja2 import Environment, FileSystemLoader + +ROOT_DIR = Path(__file__).parent.resolve() + +TRAMES_JSON = ROOT_DIR.parent / "artefacts" / "trames.json" +TEMPLATES_DIR = ROOT_DIR / "templates" +OUTPUT_DIR = ROOT_DIR.parent / "artefacts" + + +def main(): + with TRAMES_JSON.open() as f: + trames_list= json.load(f) + + env = Environment( + loader=FileSystemLoader(TEMPLATES_DIR), + trim_blocks=True, + lstrip_blocks=True, + ) + + jinja_templates_per_langs = ( + (lang_dir, template_file) + for lang_dir in TEMPLATES_DIR.iterdir() + for template_file in lang_dir.glob("*.jinja") + if lang_dir.is_dir() + ) + + for lang_dir, template_file in jinja_templates_per_langs: + rel_path = template_file.relative_to(TEMPLATES_DIR) + out_path = OUTPUT_DIR / rel_path.parent / template_file.stem + out_path.parent.mkdir(parents=True, exist_ok=True) + + template = env.get_template(str(rel_path)) + rendered = template.render( + trames=trames_list, + lang=lang_dir.name + ) + + out_path.write_text(rendered) + print(f"[+] Generated {out_path.relative_to(ROOT_DIR.parent)}") + + +if __name__ == "__main__": + main() diff --git a/generators/templates/cpp/trames.cpp.jinja b/generators/templates/cpp/trames.cpp.jinja new file mode 100644 index 0000000..e0d35a0 --- /dev/null +++ b/generators/templates/cpp/trames.cpp.jinja @@ -0,0 +1 @@ +// Auto-generated primitive diff --git a/generators/templates/python/trames.py.jinja b/generators/templates/python/trames.py.jinja new file mode 100644 index 0000000..c75049b --- /dev/null +++ b/generators/templates/python/trames.py.jinja @@ -0,0 +1 @@ +# Auto-generated primitives (Python) From 91b35a7d1d5c6a1352eb323b148233988ac22db2 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:09:55 +0100 Subject: [PATCH 05/14] Transform the trame format string into subobjects --- generators/generate_primitives.py | 59 ++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/generators/generate_primitives.py b/generators/generate_primitives.py index 1556efc..a44c135 100644 --- a/generators/generate_primitives.py +++ b/generators/generate_primitives.py @@ -1,5 +1,9 @@ import json +import re + from pathlib import Path +from typing import TypedDict, List, Dict + from jinja2 import Environment, FileSystemLoader ROOT_DIR = Path(__file__).parent.resolve() @@ -9,9 +13,62 @@ OUTPUT_DIR = ROOT_DIR.parent / "artefacts" +class TrameParam(TypedDict): + name: str + type: str + + +class Trame(TypedDict): + name: str + trame_parts: List[str] + parameters: List[TrameParam] + doc: str + + +PARAM_TYPE_MAP = { + "b": "boolean", + "i": "integer", + "s": "string", + "f": "float", +} + + +def rework_trame(trame: Dict[str, str]) -> Trame: + """ + Transform a trame with "format" into structured trame-parts and parameters. + Example: + { "name": "move_motor", "format": "20ma[i:angle]s[i:speed]" } + --> + { "name": "move_motor", + "trame_parts": ["20ma", "s"], + "parameters": [ + {"name": "angle", "type": "integer"}, + {"name": "speed", "type": "integer"} + ] + } + """ + fmt = trame["format"] + + parts = re.split(r"\[[ifs]:[^\]]+\]", fmt) + parts = [p for p in parts if p] # remove empty strings + + param_matches = re.findall(r"\[([ifs]):([^\]]+)\]", fmt) + parameters: List[TrameParam] = [ + {"name": name, "type": PARAM_TYPE_MAP.get(t, "unknown")} + for t, name in param_matches + ] + + return Trame( + name=trame["name"], + trame_parts=parts, + parameters=parameters, + doc=trame.get("doc", ""), + ) + + def main(): with TRAMES_JSON.open() as f: - trames_list= json.load(f) + trames_list = [rework_trame(trame) for trame in json.load(f)] env = Environment( loader=FileSystemLoader(TEMPLATES_DIR), From ac0a6fa210271fc9937416c9197379fb25785542 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:10:20 +0100 Subject: [PATCH 06/14] Write the template code to generate the python primitive --- generators/templates/python/trames.py.jinja | 30 ++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/generators/templates/python/trames.py.jinja b/generators/templates/python/trames.py.jinja index c75049b..5e3cf60 100644 --- a/generators/templates/python/trames.py.jinja +++ b/generators/templates/python/trames.py.jinja @@ -1 +1,29 @@ -# Auto-generated primitives (Python) +"""Auto-generated primitives for ilo communication""" + +{% set type_map = { + "boolean": "bool", + "integer": "int", + "string": "str", + "float": "float" +} %} + +{% for t in trames %} +def {{ t.name }}( + {%- for p in t.parameters -%} + {{- p.name -}}: {{ type_map[p.type] }} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} +) -> str: + {% if t.doc %} + """{{ t.doc }}""" + {% else %} + """Primitive for {{ t.name }}""" + {% endif %} + return f"{% for i in range(t.trame_parts|length) %} + {{- t.trame_parts[i] -}} + {% if i < t.parameters|length %} + {{- '{' ~ t.parameters[i].name ~ '}' -}} + {% endif %} + {% endfor %}" +{{ '\n\n' }} +{% endfor %} From ff2cce58e96b6ed6939a7eea17b40d3973941b98 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:18:22 +0100 Subject: [PATCH 07/14] Escape formatting character (fix set wifi password) --- generators/generate_primitives.py | 6 ++++++ generators/templates/python/trames.py.jinja | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/generators/generate_primitives.py b/generators/generate_primitives.py index a44c135..f0aa500 100644 --- a/generators/generate_primitives.py +++ b/generators/generate_primitives.py @@ -66,6 +66,10 @@ def rework_trame(trame: Dict[str, str]) -> Trame: ) +def escape_fstring_literal(s: str) -> str: + return s.replace("{", "{{").replace("}", "}}") + + def main(): with TRAMES_JSON.open() as f: trames_list = [rework_trame(trame) for trame in json.load(f)] @@ -76,6 +80,8 @@ def main(): lstrip_blocks=True, ) + env.filters["escape_fstring"] = escape_fstring_literal + jinja_templates_per_langs = ( (lang_dir, template_file) for lang_dir in TEMPLATES_DIR.iterdir() diff --git a/generators/templates/python/trames.py.jinja b/generators/templates/python/trames.py.jinja index 5e3cf60..6f719a4 100644 --- a/generators/templates/python/trames.py.jinja +++ b/generators/templates/python/trames.py.jinja @@ -20,7 +20,7 @@ def {{ t.name }}( """Primitive for {{ t.name }}""" {% endif %} return f"{% for i in range(t.trame_parts|length) %} - {{- t.trame_parts[i] -}} + {{- t.trame_parts[i] | escape_fstring -}} {% if i < t.parameters|length %} {{- '{' ~ t.parameters[i].name ~ '}' -}} {% endif %} From f36fe2548001d45898c560ddf7c25230a69b842e Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:20:06 +0100 Subject: [PATCH 08/14] Correct duplicated & malformed trames --- src/trame.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/trame.c b/src/trame.c index 5ea48d9..5bcb657 100644 --- a/src/trame.c +++ b/src/trame.c @@ -42,7 +42,6 @@ const trame TRAMES[] = { { "set_led_single", "55t[s:type]d[i:id]r[i:red]g[i:green]b[i:blue]" }, { "display_word", "56w[s:word]d[i:delay]/[i:nb_loops]" }, { "display_word_slide", "57" }, - { "get_product_id", "150" }, { "set_animation_flag_false", "58" }, // motors @@ -51,7 +50,7 @@ const trame TRAMES[] = { { "drive_single_motor_speed", "610i[i:motor_index]a[i:acc]v[i:speed]" }, { "get_single_motor_speed", "611i[i:motor_index]" }, { "drive_single_motor_angle", "620i[i:motor_index]a[i:acc]v[i:vel]p[i:position]" }, - { "get_single_motor_angle", "621i[:motor_index]" }, + { "get_single_motor_angle", "621i[i:motor_index]" }, { "get_single_motor_temp", "63i[i:motor_index]" }, { "get_single_motor_volt", "64i[i:motor_index]" }, { "get_single_motor_load", "65i[i:motor_index]" }, From 144367f692aa23785833dfe346c1e455465316cb Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:34:20 +0100 Subject: [PATCH 09/14] Expose primitive phony target --- Makefile | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 5b706af..fdc2d58 100644 --- a/Makefile +++ b/Makefile @@ -42,14 +42,19 @@ install: $(NAME_release) uninstall: $(RM) $(BINDIR)/$(NAME_release) -trame-exporter: +trame-exporter: src/trame.c $(LINK.c) -o $@ generators/generate-trame-json.c + @ $(LOG_TIME) "$(C_GREEN) CC $(C_PURPLE) $(notdir $@) $(C_RESET)" + +artefacts/trames.json: trame-exporter + @ ./$< > $@ + @ $(LOG_TIME) "$(C_GREEN) GEN $(C_PURPLE) $(notdir $@) $(C_RESET)" -artefacts: - @ mkdir -p $@ -artefacts/trames.json: trame-exporter artefacts - ./$< > $@ +.PHONY: primitives +primitives: artefacts/trames.json + @ python generators/generate_primitives.py + V ?= 0 ifneq ($(V),0) @@ -78,17 +83,17 @@ endif NOW = $(shell date +%s%3N) -ifndef STIME -STIME := $(call NOW) -endif +STIME := $(shell date +%s%3N) +export STIME -TIME_NS = $(shell expr $(call NOW) - $(STIME)) -TIME_MS = $(shell expr $(call TIME_NS)) +define TIME_MS +$$( expr \( $$(date +%s%3N) - $(STIME) \)) +endef BOXIFY = "[$(C_BLUE)$(1)$(C_RESET)] $(2)" ifneq ($(shell command -v printf),) - LOG_TIME = printf $(call BOXIFY, %6s ,$(strip %b\n)) "$(call TIME_MS)" + LOG_TIME = printf $(call BOXIFY, %6s , %b\n) "$(call TIME_MS)" else LOG_TIME = echo -e $(call BOXIFY, $(call TIME_MS) ,) endif From a30efc0884989080a2f6bc348ff9a5a78b9176c5 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:45:03 +0100 Subject: [PATCH 10/14] Add runtime typecheck for trames helpers --- generators/templates/python/trames.py.jinja | 45 ++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/generators/templates/python/trames.py.jinja b/generators/templates/python/trames.py.jinja index 6f719a4..148acb6 100644 --- a/generators/templates/python/trames.py.jinja +++ b/generators/templates/python/trames.py.jinja @@ -1,5 +1,36 @@ """Auto-generated primitives for ilo communication""" +import inspect +from functools import wraps +from typing import get_type_hints + + +def _typecheck(func): + hints = get_type_hints(func) + sig = inspect.signature(func) + + @wraps(func) + def wrapper(*args, **kwargs): + bound = sig.bind_partial(*args, **kwargs) + bound.apply_defaults() + + for name, value in bound.arguments.items(): + if name not in hints: + continue + + expected = hints[name] + + if not isinstance(value, expected): + raise TypeError( + f"Parameter '{name}' of '{func.__name__}' must be {expected.__name__}, " + f"got {type(value).__name__}" + ) + + return func(*args, **kwargs) + + return wrapper + + {% set type_map = { "boolean": "bool", "integer": "int", @@ -8,6 +39,7 @@ } %} {% for t in trames %} +@_typecheck def {{ t.name }}( {%- for p in t.parameters -%} {{- p.name -}}: {{ type_map[p.type] }} @@ -25,5 +57,16 @@ def {{ t.name }}( {{- '{' ~ t.parameters[i].name ~ '}' -}} {% endif %} {% endfor %}" -{{ '\n\n' }} + {%- if not loop.last -%} + {{- '\n\n' -}} + {% else %} + {{- '\n' -}} + {% endif %} +{% endfor %} + + +__all__ = ( +{% for t in trames %} + "{{ t.name }}", {% endfor %} +) From 6125b6d3e0de5246969b3ae66765c1c20bc374ec Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:52:38 +0100 Subject: [PATCH 11/14] Rename trames.{cpp,py}.jinja -> trame_builder.{cpp,py}.jinja --- .../templates/cpp/{trames.cpp.jinja => trame_builder.cpp.jinja} | 0 .../templates/python/{trames.py.jinja => trame_builder.py.jinja} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename generators/templates/cpp/{trames.cpp.jinja => trame_builder.cpp.jinja} (100%) rename generators/templates/python/{trames.py.jinja => trame_builder.py.jinja} (100%) diff --git a/generators/templates/cpp/trames.cpp.jinja b/generators/templates/cpp/trame_builder.cpp.jinja similarity index 100% rename from generators/templates/cpp/trames.cpp.jinja rename to generators/templates/cpp/trame_builder.cpp.jinja diff --git a/generators/templates/python/trames.py.jinja b/generators/templates/python/trame_builder.py.jinja similarity index 100% rename from generators/templates/python/trames.py.jinja rename to generators/templates/python/trame_builder.py.jinja From dddf0121789f8c628fdd6bd6e419169364ad54a6 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 04:53:25 +0100 Subject: [PATCH 12/14] Use primitive artefact in ilo_binding (python) --- bindings/python/MANIFEST.in | 1 - bindings/python/ilo_binding.pyi | 3 - bindings/python/ilo_binding/__init__.py | 45 ------- bindings/python/ilo_binding/trame_builder.py | 1 + bindings/python/ilo_binding_ext.pyi | 2 - bindings/python/ilo_binding_ext/core | 1 - bindings/python/ilo_binding_ext/module.c | 116 ------------------- bindings/python/setup.cfg | 2 +- 8 files changed, 2 insertions(+), 169 deletions(-) delete mode 100644 bindings/python/MANIFEST.in delete mode 100644 bindings/python/ilo_binding.pyi create mode 120000 bindings/python/ilo_binding/trame_builder.py delete mode 100644 bindings/python/ilo_binding_ext.pyi delete mode 120000 bindings/python/ilo_binding_ext/core delete mode 100644 bindings/python/ilo_binding_ext/module.c diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in deleted file mode 100644 index 300b810..0000000 --- a/bindings/python/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -global-include *.h diff --git a/bindings/python/ilo_binding.pyi b/bindings/python/ilo_binding.pyi deleted file mode 100644 index e584f63..0000000 --- a/bindings/python/ilo_binding.pyi +++ /dev/null @@ -1,3 +0,0 @@ -def get_name() -> str: - """get the name you have given to your ilo.""" - ... diff --git a/bindings/python/ilo_binding/__init__.py b/bindings/python/ilo_binding/__init__.py index 0297f8d..e69de29 100644 --- a/bindings/python/ilo_binding/__init__.py +++ b/bindings/python/ilo_binding/__init__.py @@ -1,45 +0,0 @@ -from ilo_binding_ext import build_trame, get_trames -from functools import wraps -import re - - -def __create_func(name: str, fmt: str): - matches = { - name: {"i": "int", "s": "str"}[t] - for t, name in re.findall(r"\[(\w):([^\]]+)\]", fmt) - } - - params_repr = ', '.join( - f"{name}: {type_}" - for name, type_ in matches.items() - ) - - namespace = {} - exec(f"def {name}({params_repr}) -> str: ...", namespace) - - @wraps(namespace[name]) - def wrapped(**kwargs): - if len(kwargs) != len(matches): - raise TypeError("Invalid number of arguments.") - - for k, v in kwargs.items(): - p = matches.get(k) - if p is None: - continue - - if type(v).__name__ != p: - raise TypeError( - f"Expected `{p}` for parameter {k}, but got `{type(v).__name__}`" - ) - - return build_trame( - name, - tuple((n, f"{f}") for n, f in kwargs.items()) - ) - - return wrapped - -locals().update(**{ - name: __create_func(name, fmt) - for name, fmt in get_trames() -}) diff --git a/bindings/python/ilo_binding/trame_builder.py b/bindings/python/ilo_binding/trame_builder.py new file mode 120000 index 0000000..f9080a5 --- /dev/null +++ b/bindings/python/ilo_binding/trame_builder.py @@ -0,0 +1 @@ +../../../artefacts/python/trame_builder.py \ No newline at end of file diff --git a/bindings/python/ilo_binding_ext.pyi b/bindings/python/ilo_binding_ext.pyi deleted file mode 100644 index fbf7f83..0000000 --- a/bindings/python/ilo_binding_ext.pyi +++ /dev/null @@ -1,2 +0,0 @@ -def build_trame(name: str, params: tuple[tuple[str, str], ...]) -> str: ... -def get_trames() -> tuple[tuple[str, str]]: ... diff --git a/bindings/python/ilo_binding_ext/core b/bindings/python/ilo_binding_ext/core deleted file mode 120000 index dabb0e1..0000000 --- a/bindings/python/ilo_binding_ext/core +++ /dev/null @@ -1 +0,0 @@ -../../../src \ No newline at end of file diff --git a/bindings/python/ilo_binding_ext/module.c b/bindings/python/ilo_binding_ext/module.c deleted file mode 100644 index 88218db..0000000 --- a/bindings/python/ilo_binding_ext/module.c +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -#include "core/trame.h" - -static -PyObject *py_get_trames(PyObject *self, PyObject *args) -{ - PyObject *tuple; - PyObject *list = PyList_New(0); - if (!list) return NULL; - - (void)self, (void)args; - - for (int i = 0; i < TRAME_COUNT; i++) { - PyObject *d = Py_BuildValue( - "(s,s)", - TRAMES[i].name, - TRAMES[i].format - ); - - if (d == NULL) { - Py_DECREF(list); - return NULL; - } - PyList_Append(list, d); - Py_DECREF(d); - } - - tuple = PyList_AsTuple(list); - Py_DECREF(list); - return tuple; -} - -static -PyObject *py_build_trame(PyObject *self, PyObject *args) -{ - trame_param t_params[8]; - - PyObject *param_tuple; - char const *trame_name; - - (void)self; - if (!PyArg_ParseTuple(args, "sO!", &trame_name, &PyTuple_Type, ¶m_tuple)) - return NULL; - - Py_ssize_t n = PyTuple_Size(param_tuple); - - if (n >= (int)countof(t_params)) { - PyErr_SetString(PyExc_ValueError, "too many parameters"); - return NULL; - } - - char *result = NULL; - - for (Py_ssize_t i = 0; i < n; i++) { - PyObject *item = PyTuple_GetItem(param_tuple, i); - if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) { - PyErr_Format(PyExc_TypeError, - "param %zd must be a tuple of (str, str)", i); - return NULL; - } - - PyObject *py_name = PyTuple_GetItem(item, 0); - PyObject *py_value = PyTuple_GetItem(item, 1); - - if (!PyUnicode_Check(py_name) || !PyUnicode_Check(py_value)) { - PyErr_Format(PyExc_TypeError, - "param %zd elements must be str", i); - return NULL; - } - - t_params[i].name = PyUnicode_AsUTF8(py_name); - t_params[i].value = PyUnicode_AsUTF8(py_value); - - if (t_params[i].name == NULL || t_params[i].value == NULL) - return NULL; - } - - result = exposed_build_trame(trame_name, n, t_params); - if (!result) { - PyErr_Format(PyExc_RuntimeError, "build_trame failed for %s", trame_name); - return NULL; - } - - return PyUnicode_FromString(result); -} - -static -PyMethodDef ILO_BINDING_METHODS[] = { - { "get_trames", py_get_trames, METH_VARARGS, NULL }, - { "build_trame", py_build_trame, METH_VARARGS, NULL }, - { NULL, NULL, 0, NULL } -}; - -static -struct PyModuleDef MODULE = { - PyModuleDef_HEAD_INIT, - .m_name = "ilo_binding_ext", - .m_doc = NULL, - .m_size = -1, - .m_methods = ILO_BINDING_METHODS -}; - -// NOLINTNEXTLINE(readability-identifier-naming): mendatory func name -PyMODINIT_FUNC PyInit_ilo_binding_ext(void) -{ - PyObject *mod = PyModule_Create(&MODULE); - - return mod; -} diff --git a/bindings/python/setup.cfg b/bindings/python/setup.cfg index 8a16150..530da53 100644 --- a/bindings/python/setup.cfg +++ b/bindings/python/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ilo_binding -version = 0.0.1 +version = 1.0.0 description = Python binding for the ilo protocol author = Sigmanificient author_email = yohann.boniface@epitech.eu From 7cb914afdf65f89e7d8b7f722d17f877101f29e6 Mon Sep 17 00:00:00 2001 From: Sigmanificient Date: Thu, 4 Dec 2025 05:04:00 +0100 Subject: [PATCH 13/14] Setup artefacts generation CI --- .gitattributes | 4 ++ .github/workflows/generate_artefacts.yml | 47 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/generate_artefacts.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6f7f8bc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +artefacts/** linguist-generated + +* text=lf +* eol=lf diff --git a/.github/workflows/generate_artefacts.yml b/.github/workflows/generate_artefacts.yml new file mode 100644 index 0000000..7ab41cc --- /dev/null +++ b/.github/workflows/generate_artefacts.yml @@ -0,0 +1,47 @@ +name: Generate Artefacts + +on: + push: + paths: + - "generators/**" + - "src/**" + - ".github/workflows/generate_artefacts.yml" + workflow_dispatch: + +jobs: + generate-and-push: + runs-on: ubuntu-latest + + permissions: + contents: write + actions: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Python deps + run: | + pip install jinja2 --disable-pip-version-check + + - name: Run primitive generator + run: | + make primitives + + - name: Commit artefacts + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add -f artefacts/ + + git commit -m "Regenerate artefacts automatically [skip ci]" + git push From 1a646fc4da77d738d762c66368b754f09a7b8c68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 04:05:04 +0000 Subject: [PATCH 14/14] Regenerate artefacts automatically [skip ci] --- artefacts/cpp/trame_builder.cpp | 1 + artefacts/python/trame_builder.py | 475 ++++++++++++++++++++++++++++++ artefacts/trames.json | 75 +++++ 3 files changed, 551 insertions(+) create mode 100644 artefacts/cpp/trame_builder.cpp create mode 100644 artefacts/python/trame_builder.py create mode 100644 artefacts/trames.json diff --git a/artefacts/cpp/trame_builder.cpp b/artefacts/cpp/trame_builder.cpp new file mode 100644 index 0000000..3f77ed7 --- /dev/null +++ b/artefacts/cpp/trame_builder.cpp @@ -0,0 +1 @@ +// Auto-generated primitive \ No newline at end of file diff --git a/artefacts/python/trame_builder.py b/artefacts/python/trame_builder.py new file mode 100644 index 0000000..c0c7840 --- /dev/null +++ b/artefacts/python/trame_builder.py @@ -0,0 +1,475 @@ +"""Auto-generated primitives for ilo communication""" + +import inspect +from functools import wraps +from typing import get_type_hints + + +def _typecheck(func): + hints = get_type_hints(func) + sig = inspect.signature(func) + + @wraps(func) + def wrapper(*args, **kwargs): + bound = sig.bind_partial(*args, **kwargs) + bound.apply_defaults() + + for name, value in bound.arguments.items(): + if name not in hints: + continue + + expected = hints[name] + + if not isinstance(value, expected): + raise TypeError( + f"Parameter '{name}' of '{func.__name__}' must be {expected.__name__}, " + f"got {type(value).__name__}" + ) + + return func(*args, **kwargs) + + return wrapper + + + +@_typecheck +def safety_stop() -> str: + """Primitive for safety_stop""" + return f"" + +@_typecheck +def handshake_ilo() -> str: + """Primitive for handshake_ilo""" + return f"ilo" + +@_typecheck +def get_robot_version() -> str: + """Primitive for get_robot_version""" + return f"500y" + +@_typecheck +def start_firmware_upload(size: int) -> str: + """Primitive for start_firmware_upload""" + return f"500x{size}" + +@_typecheck +def start_trame_s(trame_s_params: str) -> str: + """Primitive for start_trame_s""" + return f"0{trame_s_params}" + +@_typecheck +def stop_tasks() -> str: + """Primitive for stop_tasks""" + return f"00" + +@_typecheck +def get_color_rgb_center() -> str: + """Primitive for get_color_rgb_center""" + return f"10c" + +@_typecheck +def get_color_rgb_left() -> str: + """Primitive for get_color_rgb_left""" + return f"10l" + +@_typecheck +def get_color_rgb_right() -> str: + """Primitive for get_color_rgb_right""" + return f"10d" + +@_typecheck +def get_color_clear() -> str: + """Primitive for get_color_clear""" + return f"11" + +@_typecheck +def get_line() -> str: + """Primitive for get_line""" + return f"12" + +@_typecheck +def set_line_threshold_value(threshold: int) -> str: + """Primitive for set_line_threshold_value""" + return f"13t{threshold}" + +@_typecheck +def get_line_threshold_value() -> str: + """Primitive for get_line_threshold_value""" + return f"14" + +@_typecheck +def get_accessory_status() -> str: + """Primitive for get_accessory_status""" + return f"15" + +@_typecheck +def get_sensor_distance() -> str: + """Primitive for get_sensor_distance""" + return f"20" + +@_typecheck +def get_distance_front() -> str: + """Primitive for get_distance_front""" + return f"21" + +@_typecheck +def get_distance_right() -> str: + """Primitive for get_distance_right""" + return f"22" + +@_typecheck +def get_distance_back() -> str: + """Primitive for get_distance_back""" + return f"23" + +@_typecheck +def get_distance_left() -> str: + """Primitive for get_distance_left""" + return f"24" + +@_typecheck +def get_imu_info() -> str: + """Primitive for get_imu_info""" + return f"30" + +@_typecheck +def reset_angle() -> str: + """Primitive for reset_angle""" + return f"31" + +@_typecheck +def get_raw_imu() -> str: + """Primitive for get_raw_imu""" + return f"32" + +@_typecheck +def get_battery_info() -> str: + """Primitive for get_battery_info""" + return f"40" + +@_typecheck +def get_led_color() -> str: + """Primitive for get_led_color""" + return f"50" + +@_typecheck +def set_led_color(red: int, green: int, blue: int) -> str: + """Primitive for set_led_color""" + return f"51r{red}g{green}b{blue}" + +@_typecheck +def set_led_shape(shape: str) -> str: + """Primitive for set_led_shape""" + return f"52v{shape}" + +@_typecheck +def set_led_mode(mode: str, nb_loop: int) -> str: + """Primitive for set_led_mode""" + return f"53{mode}/{nb_loop}" + +@_typecheck +def set_led_captor(brightness: int) -> str: + """Primitive for set_led_captor""" + return f"54l{brightness}" + +@_typecheck +def set_led_single(type: str, id: int, red: int, green: int, blue: int) -> str: + """Primitive for set_led_single""" + return f"55t{type}d{id}r{red}g{green}b{blue}" + +@_typecheck +def display_word(word: str, delay: int, nb_loops: int) -> str: + """Primitive for display_word""" + return f"56w{word}d{delay}/{nb_loops}" + +@_typecheck +def display_word_slide() -> str: + """Primitive for display_word_slide""" + return f"57" + +@_typecheck +def set_animation_flag_false() -> str: + """Primitive for set_animation_flag_false""" + return f"58" + +@_typecheck +def run_command_motor(params: str) -> str: + """Primitive for run_command_motor""" + return f"a{params}" + +@_typecheck +def ping_motor(ping_status_0: int, ping_status_1: int) -> str: + """Primitive for ping_motor""" + return f"60i{ping_status_0}s{ping_status_1}" + +@_typecheck +def drive_single_motor_speed(motor_index: int, acc: int, speed: int) -> str: + """Primitive for drive_single_motor_speed""" + return f"610i{motor_index}a{acc}v{speed}" + +@_typecheck +def get_single_motor_speed(motor_index: int) -> str: + """Primitive for get_single_motor_speed""" + return f"611i{motor_index}" + +@_typecheck +def drive_single_motor_angle(motor_index: int, acc: int, vel: int, position: int) -> str: + """Primitive for drive_single_motor_angle""" + return f"620i{motor_index}a{acc}v{vel}p{position}" + +@_typecheck +def get_single_motor_angle(motor_index: int) -> str: + """Primitive for get_single_motor_angle""" + return f"621i{motor_index}" + +@_typecheck +def get_single_motor_temp(motor_index: int) -> str: + """Primitive for get_single_motor_temp""" + return f"63i{motor_index}" + +@_typecheck +def get_single_motor_volt(motor_index: int) -> str: + """Primitive for get_single_motor_volt""" + return f"64i{motor_index}" + +@_typecheck +def get_single_motor_load(motor_index: int) -> str: + """Primitive for get_single_motor_load""" + return f"65i{motor_index}" + +@_typecheck +def get_single_motor_current(motor_index: int) -> str: + """Primitive for get_single_motor_current""" + return f"66i{motor_index}" + +@_typecheck +def get_single_motor_move(motor_index: int) -> str: + """Primitive for get_single_motor_move""" + return f"67i{motor_index}" + +@_typecheck +def set_motors_ilo_acc(acc: int) -> str: + """Primitive for set_motors_ilo_acc""" + return f"680a{acc}" + +@_typecheck +def get_motors_ilo_acc() -> str: + """Primitive for get_motors_ilo_acc""" + return f"681" + +@_typecheck +def set_tempo_pos(tempo_pos: int) -> str: + """Primitive for set_tempo_pos""" + return f"690t{tempo_pos}" + +@_typecheck +def get_tempo_pos() -> str: + """Primitive for get_tempo_pos""" + return f"691" + +@_typecheck +def set_pid(Kp: int, Ki: int, Kd: int) -> str: + """Primitive for set_pid""" + return f"70p{Kp}i{Ki}d{Kd}" + +@_typecheck +def get_pid() -> str: + """Primitive for get_pid""" + return f"71" + +@_typecheck +def check_auto_mode(current_auto_mode: int) -> str: + """Primitive for check_auto_mode""" + return f"80{current_auto_mode}" + +@_typecheck +def set_wifi_credentials(ssid: str, password: str) -> str: + """Primitive for set_wifi_credentials""" + return f"90{ssid}{{|||}}{password}" + +@_typecheck +def get_wifi_credentials() -> str: + """Primitive for get_wifi_credentials""" + return f"92" + +@_typecheck +def get_hostname() -> str: + """Primitive for get_hostname""" + return f"93" + +@_typecheck +def get_hostname_legacy() -> str: + """Primitive for get_hostname_legacy""" + return f"930" + +@_typecheck +def set_name(name: str) -> str: + """Primitive for set_name""" + return f"94n{name}" + +@_typecheck +def set_server_status(status: int) -> str: + """Primitive for set_server_status""" + return f"95s{status}" + +@_typecheck +def get_server_status() -> str: + """Primitive for get_server_status""" + return f"96" + +@_typecheck +def get_accessory_data() -> str: + """Primitive for get_accessory_data""" + return f"100" + +@_typecheck +def get_accessory_info() -> str: + """Primitive for get_accessory_info""" + return f"101" + +@_typecheck +def very_very_usefull() -> str: + """Primitive for very_very_usefull""" + return f"102" + +@_typecheck +def set_debug_state(state: int) -> str: + """Primitive for set_debug_state""" + return f"103s{state}" + +@_typecheck +def start_diag() -> str: + """Primitive for start_diag""" + return f"110" + +@_typecheck +def get_manufacturing_date() -> str: + """Primitive for get_manufacturing_date""" + return f"120" + +@_typecheck +def set_manufacturing_date(date: str) -> str: + """Primitive for set_manufacturing_date""" + return f"121s{date}" + +@_typecheck +def get_first_use_date() -> str: + """Primitive for get_first_use_date""" + return f"130" + +@_typecheck +def set_first_use_date(date: str) -> str: + """Primitive for set_first_use_date""" + return f"131s{date}" + +@_typecheck +def get_product_version() -> str: + """Primitive for get_product_version""" + return f"140" + +@_typecheck +def set_product_version(version: str) -> str: + """Primitive for set_product_version""" + return f"141s{version}" + +@_typecheck +def get_product_id() -> str: + """Primitive for get_product_id""" + return f"150" + +@_typecheck +def set_product_id(product_id: str) -> str: + """Primitive for set_product_id""" + return f"151s{product_id}" + +@_typecheck +def get_review_date() -> str: + """Primitive for get_review_date""" + return f"160" + +@_typecheck +def set_review_date(date: str) -> str: + """Primitive for set_review_date""" + return f"161s{date}" + +@_typecheck +def set_auto_setup(auto_setup: int) -> str: + """Primitive for set_auto_setup""" + return f"170a{auto_setup}" + + +__all__ = ( + "safety_stop", + "handshake_ilo", + "get_robot_version", + "start_firmware_upload", + "start_trame_s", + "stop_tasks", + "get_color_rgb_center", + "get_color_rgb_left", + "get_color_rgb_right", + "get_color_clear", + "get_line", + "set_line_threshold_value", + "get_line_threshold_value", + "get_accessory_status", + "get_sensor_distance", + "get_distance_front", + "get_distance_right", + "get_distance_back", + "get_distance_left", + "get_imu_info", + "reset_angle", + "get_raw_imu", + "get_battery_info", + "get_led_color", + "set_led_color", + "set_led_shape", + "set_led_mode", + "set_led_captor", + "set_led_single", + "display_word", + "display_word_slide", + "set_animation_flag_false", + "run_command_motor", + "ping_motor", + "drive_single_motor_speed", + "get_single_motor_speed", + "drive_single_motor_angle", + "get_single_motor_angle", + "get_single_motor_temp", + "get_single_motor_volt", + "get_single_motor_load", + "get_single_motor_current", + "get_single_motor_move", + "set_motors_ilo_acc", + "get_motors_ilo_acc", + "set_tempo_pos", + "get_tempo_pos", + "set_pid", + "get_pid", + "check_auto_mode", + "set_wifi_credentials", + "get_wifi_credentials", + "get_hostname", + "get_hostname_legacy", + "set_name", + "set_server_status", + "get_server_status", + "get_accessory_data", + "get_accessory_info", + "very_very_usefull", + "set_debug_state", + "start_diag", + "get_manufacturing_date", + "set_manufacturing_date", + "get_first_use_date", + "set_first_use_date", + "get_product_version", + "set_product_version", + "get_product_id", + "set_product_id", + "get_review_date", + "set_review_date", + "set_auto_setup", +) \ No newline at end of file diff --git a/artefacts/trames.json b/artefacts/trames.json new file mode 100644 index 0000000..0dc6fb2 --- /dev/null +++ b/artefacts/trames.json @@ -0,0 +1,75 @@ +[ + { "name": "safety_stop", "format": "" }, + { "name": "handshake_ilo", "format": "ilo" }, + { "name": "get_robot_version", "format": "500y" }, + { "name": "start_firmware_upload", "format": "500x[i:size]" }, + { "name": "start_trame_s", "format": "0[s:trame_s_params]" }, + { "name": "stop_tasks", "format": "00" }, + { "name": "get_color_rgb_center", "format": "10c" }, + { "name": "get_color_rgb_left", "format": "10l" }, + { "name": "get_color_rgb_right", "format": "10d" }, + { "name": "get_color_clear", "format": "11" }, + { "name": "get_line", "format": "12" }, + { "name": "set_line_threshold_value", "format": "13t[i:threshold]" }, + { "name": "get_line_threshold_value", "format": "14" }, + { "name": "get_accessory_status", "format": "15" }, + { "name": "get_sensor_distance", "format": "20" }, + { "name": "get_distance_front", "format": "21" }, + { "name": "get_distance_right", "format": "22" }, + { "name": "get_distance_back", "format": "23" }, + { "name": "get_distance_left", "format": "24" }, + { "name": "get_imu_info", "format": "30" }, + { "name": "reset_angle", "format": "31" }, + { "name": "get_raw_imu", "format": "32" }, + { "name": "get_battery_info", "format": "40" }, + { "name": "get_led_color", "format": "50" }, + { "name": "set_led_color", "format": "51r[i:red]g[i:green]b[i:blue]" }, + { "name": "set_led_shape", "format": "52v[s:shape]" }, + { "name": "set_led_mode", "format": "53[s:mode]/[i:nb_loop]" }, + { "name": "set_led_captor", "format": "54l[i:brightness]" }, + { "name": "set_led_single", "format": "55t[s:type]d[i:id]r[i:red]g[i:green]b[i:blue]" }, + { "name": "display_word", "format": "56w[s:word]d[i:delay]/[i:nb_loops]" }, + { "name": "display_word_slide", "format": "57" }, + { "name": "set_animation_flag_false", "format": "58" }, + { "name": "run_command_motor", "format": "a[s:params]" }, + { "name": "ping_motor", "format": "60i[i:ping_status_0]s[i:ping_status_1]" }, + { "name": "drive_single_motor_speed", "format": "610i[i:motor_index]a[i:acc]v[i:speed]" }, + { "name": "get_single_motor_speed", "format": "611i[i:motor_index]" }, + { "name": "drive_single_motor_angle", "format": "620i[i:motor_index]a[i:acc]v[i:vel]p[i:position]" }, + { "name": "get_single_motor_angle", "format": "621i[i:motor_index]" }, + { "name": "get_single_motor_temp", "format": "63i[i:motor_index]" }, + { "name": "get_single_motor_volt", "format": "64i[i:motor_index]" }, + { "name": "get_single_motor_load", "format": "65i[i:motor_index]" }, + { "name": "get_single_motor_current", "format": "66i[i:motor_index]" }, + { "name": "get_single_motor_move", "format": "67i[i:motor_index]" }, + { "name": "set_motors_ilo_acc", "format": "680a[i:acc]" }, + { "name": "get_motors_ilo_acc", "format": "681" }, + { "name": "set_tempo_pos", "format": "690t[i:tempo_pos]" }, + { "name": "get_tempo_pos", "format": "691" }, + { "name": "set_pid", "format": "70p[i:Kp]i[i:Ki]d[i:Kd]" }, + { "name": "get_pid", "format": "71" }, + { "name": "check_auto_mode", "format": "80[i:current_auto_mode]" }, + { "name": "set_wifi_credentials", "format": "90[s:ssid]{|||}[s:password]" }, + { "name": "get_wifi_credentials", "format": "92" }, + { "name": "get_hostname", "format": "93" }, + { "name": "get_hostname_legacy", "format": "930" }, + { "name": "set_name", "format": "94n[s:name]" }, + { "name": "set_server_status", "format": "95s[i:status]" }, + { "name": "get_server_status", "format": "96" }, + { "name": "get_accessory_data", "format": "100" }, + { "name": "get_accessory_info", "format": "101" }, + { "name": "very_very_usefull", "format": "102" }, + { "name": "set_debug_state", "format": "103s[i:state]" }, + { "name": "start_diag", "format": "110" }, + { "name": "get_manufacturing_date", "format": "120" }, + { "name": "set_manufacturing_date", "format": "121s[s:date]" }, + { "name": "get_first_use_date", "format": "130" }, + { "name": "set_first_use_date", "format": "131s[s:date]" }, + { "name": "get_product_version", "format": "140" }, + { "name": "set_product_version", "format": "141s[s:version]" }, + { "name": "get_product_id", "format": "150" }, + { "name": "set_product_id", "format": "151s[s:product_id]" }, + { "name": "get_review_date", "format": "160" }, + { "name": "set_review_date", "format": "161s[s:date]" }, + { "name": "set_auto_setup", "format": "170a[i:auto_setup]" } +]