diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51818a7f3..f7721b726 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,6 +55,8 @@ jobs: clang --version clang-format --version pylint --version + python3 --version + scons --version - name: Format and lint run: | diff --git a/.gitignore b/.gitignore index 7264aa76b..4a64b45a2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ __pycache__ *can_board_ids.h *system_can.dbc x86_flash + +py/can/messages.py diff --git a/SConstruct b/SConstruct index 65b44a67c..71ac41fc4 100644 --- a/SConstruct +++ b/SConstruct @@ -164,14 +164,16 @@ if PLATFORM == 'x86' and TARGET: # os.exec the x86 project ELF file to simulate it def sim_run(target, source, env): - print('Simulating', project_elf) - subprocess.run([project_elf.path]) + path = source[0].path + print('Simulating', path) + subprocess.run([path]) AlwaysBuild(Command('#/sim', project_elf, sim_run)) # open gdb with the elf file def gdb_run(target, source, env): - subprocess.run(['/usr/bin/gdb', project_elf.path]) + path = source[0].path + subprocess.run(['/usr/bin/gdb', path]) AlwaysBuild(Command('#/gdb', project_elf, gdb_run)) @@ -188,9 +190,10 @@ if PLATFORM == 'arm' and TARGET: # flash the MCU using openocd def flash_run_target(target, source, env): - serialData = flash_run(project_bin) + serialData = flash_run(source) + print("\nSerial output:") while True: line: str = serialData.readline().decode("utf-8") print(line, end='') - AlwaysBuild(Command('#/flash', project_bin, flash_run_target)) \ No newline at end of file + AlwaysBuild(Command('#/flash', project_bin, flash_run_target)) diff --git a/autogen/__main__.py b/autogen/__main__.py new file mode 100644 index 000000000..660ad560a --- /dev/null +++ b/autogen/__main__.py @@ -0,0 +1,52 @@ +import argparse +import jinja2 +from pathlib import Path + + +def get_file_name(template_name, board): + # get the name of the jinja file from the filepath + jinja_prefix = Path(template_name).stem + # files that start with _ are generic and we want to prepend the board name + if jinja_prefix.startswith('_') and board != None: + return board + jinja_prefix + else: + return jinja_prefix + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("template_name") + parser.add_argument("-o", dest="output", required=True, metavar="DIR") + + args = parser.parse_args() + + if args.template_name in ["can", "can_board_ids", "python_can"]: + from .can import get_data + elif args.template_name in ["test"]: + from .test import get_data + elif args.template_name in ["new", "new_py"]: + from .new import get_data + elif args.template_name in ["new_task"]: + from .new_task import get_data + + data = get_data(args) + + template_dir = Path("autogen/templates", args.template_name) + template_loader = jinja2.FileSystemLoader(searchpath=template_dir) + env = jinja2.Environment(loader=template_loader) + env.tests["contains"] = (lambda list, var: (var in list)) + + for template in template_dir.glob("**/*.jinja"): + template_path = str(template.relative_to(template_dir)) + + output = env.get_template(template_path).render(**data) + output_file_name = env.from_string(template_path).render(**data) + output_file = Path(args.output, output_file_name).with_suffix("") + output_file.parent.mkdir(parents=True, exist_ok=True) + output_file.write_text(output) + + print("Done autogenerating") + + +if __name__ == "__main__": + main() diff --git a/libraries/codegen/generator.py b/autogen/can.py similarity index 63% rename from libraries/codegen/generator.py rename to autogen/can.py index 38172d1aa..50a99fcb9 100644 --- a/libraries/codegen/generator.py +++ b/autogen/can.py @@ -1,20 +1,8 @@ -import argparse -import jinja2 -import yaml import re +import yaml from pathlib import Path -def get_file_name(template_name, board): - # get the name of the jinja file from the filepath - jinja_prefix = Path(template_name).stem - # files that start with _ are generic and we want to prepend the board name - if jinja_prefix.startswith('_') and board != None: - return board + jinja_prefix - else: - return jinja_prefix - - def check_yaml_file(data): illegal_chars_regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') message_ids = set() @@ -55,11 +43,16 @@ def check_yaml_file(data): raise Exception("Message must be 64 bits or less") -def get_data(): +def get_data(args): + ''' + get data to generate the can files for a board + expect args.output to be ...///can + where is one of libraries/projects/smoke + ''' boards = [] messages = [] - for yaml_path in Path(__file__).parent.glob("boards/*.yaml"): + for yaml_path in Path("can/boards").glob("*.yaml"): # read yaml with open(yaml_path, "r") as f: data = yaml.load(f, Loader=yaml.FullLoader) @@ -92,31 +85,6 @@ def get_data(): "receiver": message["target"], }) - return {"Boards": boards, "Messages": messages} - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("-t", "--template", nargs='+', default=[], dest="templates", - action="append", help="template file to populate", metavar="FILE") - parser.add_argument("-f", "--file_path", default=[], dest="outputs", - action="append", help="output directory path", metavar="DIR") - parser.add_argument("-b", dest="board", default=None) - - args = parser.parse_args() - data = get_data() - data.update({"Board": args.board}) - - template_loader = jinja2.FileSystemLoader( - searchpath=Path(__file__).parent.joinpath("templates").as_posix()) - env = jinja2.Environment(loader=template_loader) - env.tests["contains"] = (lambda list, var: (var in list)) - - for output_dir, templates in zip(args.outputs, args.templates): - for template in templates: - output = env.get_template(template).render(data=data) - Path(output_dir, get_file_name(template, args.board)).write_text(output) - + board = Path(args.output).parent.stem -if __name__ == "__main__": - main() + return {"boards": boards, "messages": messages, "board": board} diff --git a/autogen/new.py b/autogen/new.py new file mode 100644 index 000000000..d961482ae --- /dev/null +++ b/autogen/new.py @@ -0,0 +1,20 @@ +from pathlib import Path + + +def get_data(args): + project_name = Path(args.output).stem + project_type = Path(args.output).parent.stem + + libs = [] + if project_type in ["projects", "smoke"]: + # default libs + libs = ["FreeRTOS", "ms-common"] + + if project_type == "py": + args.template_name = "new_py" + + if Path(args.output).exists(): + raise (f"{project_type}/{project_name} already exists") + + return {"proj_name": project_name, "libs": libs } + diff --git a/autogen/new_task.py b/autogen/new_task.py new file mode 100644 index 000000000..08fdd2f62 --- /dev/null +++ b/autogen/new_task.py @@ -0,0 +1,10 @@ +from pathlib import Path + + +def get_data(args): + task_name = Path(args.output).stem + proj_name = Path(args.output).parent.stem + + args.output = str(Path(args.output).parent) + + return {"proj_name": proj_name, "task_name": task_name} diff --git a/libraries/codegen/templates/can_codegen.h.jinja b/autogen/templates/can/inc/can_codegen.h.jinja similarity index 92% rename from libraries/codegen/templates/can_codegen.h.jinja rename to autogen/templates/can/inc/can_codegen.h.jinja index bafdbb78e..d79fcf279 100644 --- a/libraries/codegen/templates/can_codegen.h.jinja +++ b/autogen/templates/can/inc/can_codegen.h.jinja @@ -1,5 +1,3 @@ -{% set board = data["Board"] -%} - #pragma once #include "{{board}}_rx_structs.h" diff --git a/libraries/codegen/templates/_filters.h.jinja b/autogen/templates/can/inc/{{board}}_filters.h.jinja similarity index 67% rename from libraries/codegen/templates/_filters.h.jinja rename to autogen/templates/can/inc/{{board}}_filters.h.jinja index 935353980..19c45f25c 100644 --- a/libraries/codegen/templates/_filters.h.jinja +++ b/autogen/templates/can/inc/{{board}}_filters.h.jinja @@ -1,5 +1,4 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("receiver", "contains", board) | list -%} +{% set messages = messages | selectattr("receiver", "contains", board) | list -%} #pragma once diff --git a/libraries/codegen/templates/_getters.h.jinja b/autogen/templates/can/inc/{{board}}_getters.h.jinja similarity index 72% rename from libraries/codegen/templates/_getters.h.jinja rename to autogen/templates/can/inc/{{board}}_getters.h.jinja index 3dc6859ed..312689b87 100644 --- a/libraries/codegen/templates/_getters.h.jinja +++ b/autogen/templates/can/inc/{{board}}_getters.h.jinja @@ -1,5 +1,4 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("receiver", "contains", board) | list -%} +{% set messages = messages | selectattr("receiver", "contains", board) | list -%} #pragma once diff --git a/libraries/codegen/templates/_rx_structs.h.jinja b/autogen/templates/can/inc/{{board}}_rx_structs.h.jinja similarity index 72% rename from libraries/codegen/templates/_rx_structs.h.jinja rename to autogen/templates/can/inc/{{board}}_rx_structs.h.jinja index ad8135495..5e9111026 100644 --- a/libraries/codegen/templates/_rx_structs.h.jinja +++ b/autogen/templates/can/inc/{{board}}_rx_structs.h.jinja @@ -1,18 +1,11 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("receiver", "contains", board) | list -%} +{% set messages = messages | selectattr("receiver", "contains", board) | list -%} #pragma once #include #include -#include "can_watchdog.h" -{% for message in messages %} - {%- if message.receiver[board].watchdog != 0 %} -#define check_{{message.name}}_msg_watchdog() \ - s_{{message.name}}_msg_watchdog.missed - {%- endif %} -{%- endfor %} +#include "can_watchdog.h" typedef struct { @@ -28,6 +21,9 @@ typedef struct {% for message in messages %} {%- if message.receiver[board].watchdog != 0 %} +#define check_{{message.name}}_msg_watchdog() \ + s_{{message.name}}_msg_watchdog.missed + extern CanWatchDog s_{{message.name}}_msg_watchdog; {%- endif %} {%- endfor %} diff --git a/libraries/codegen/templates/_setters.h.jinja b/autogen/templates/can/inc/{{board}}_setters.h.jinja similarity index 68% rename from libraries/codegen/templates/_setters.h.jinja rename to autogen/templates/can/inc/{{board}}_setters.h.jinja index 28864b7fc..613425885 100644 --- a/libraries/codegen/templates/_setters.h.jinja +++ b/autogen/templates/can/inc/{{board}}_setters.h.jinja @@ -1,5 +1,4 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("sender", "eq", board) | list -%} +{% set messages = messages | selectattr("sender", "eq", board) | list -%} #pragma once diff --git a/libraries/codegen/templates/_tx_structs.h.jinja b/autogen/templates/can/inc/{{board}}_tx_structs.h.jinja similarity index 67% rename from libraries/codegen/templates/_tx_structs.h.jinja rename to autogen/templates/can/inc/{{board}}_tx_structs.h.jinja index e86c78fc0..b5f9dd340 100644 --- a/libraries/codegen/templates/_tx_structs.h.jinja +++ b/autogen/templates/can/inc/{{board}}_tx_structs.h.jinja @@ -1,5 +1,4 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("sender", "eq", board) | list -%} +{% set messages = messages | selectattr("sender", "eq", board) | list -%} #pragma once diff --git a/libraries/codegen/templates/_rx_all.c.jinja b/autogen/templates/can/src/{{board}}_rx_all.c.jinja similarity index 84% rename from libraries/codegen/templates/_rx_all.c.jinja rename to autogen/templates/can/src/{{board}}_rx_all.c.jinja index a33973f05..b73b5f179 100644 --- a/libraries/codegen/templates/_rx_all.c.jinja +++ b/autogen/templates/can/src/{{board}}_rx_all.c.jinja @@ -1,11 +1,10 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("receiver", "contains", board) | list -%} +{% set messages = messages | selectattr("receiver", "contains", board) | list -%} #include "can_board_ids.h" #include "can_codegen.h" #include "can_watchdog.h" -{% for message in messages %} +{%- for message in messages %} {%- if message.receiver[board].watchdog != 0 %} CanWatchDog s_{{message.name}}_msg_watchdog = {0, {{message.receiver[board].watchdog | lower}}, 0}; {%- endif %} @@ -43,7 +42,6 @@ void clear_rx_received() { } StatusCode check_can_watchdogs() { - StatusCode status = STATUS_CODE_OK; {%- for message in messages %} {%- if message.receiver[board].watchdog != 0 %} if (!g_rx_struct.received_{{message.name}}) { @@ -52,14 +50,11 @@ StatusCode check_can_watchdogs() { LOG_CRITICAL("DID NOT RECEIVE CAN MESSAGE: %u IN MAX CYCLES : %u\n", SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, s_{{message.name}}_msg_watchdog.max_cycles); s_{{message.name}}_msg_watchdog.missed = 1; - status = STATUS_CODE_TIMEOUT; + } else { + s_{{message.name}}_msg_watchdog.missed = 0; } - } else { - s_{{message.name}}_msg_watchdog.cycles_over = 0; - s_{{message.name}}_msg_watchdog.missed = 0; } {%- endif %} {%- endfor %} - return status; + return STATUS_CODE_OK; } - diff --git a/libraries/codegen/templates/_tx_all.c.jinja b/autogen/templates/can/src/{{board}}_tx_all.c.jinja similarity index 87% rename from libraries/codegen/templates/_tx_all.c.jinja rename to autogen/templates/can/src/{{board}}_tx_all.c.jinja index de78aaff1..0b04ef698 100644 --- a/libraries/codegen/templates/_tx_all.c.jinja +++ b/autogen/templates/can/src/{{board}}_tx_all.c.jinja @@ -1,5 +1,4 @@ -{% set board = data["Board"] -%} -{% set messages = data["Messages"] | selectattr("sender", "eq", board) | list -%} +{% set messages = messages | selectattr("sender", "eq", board) | list -%} #include diff --git a/libraries/codegen/templates/can_board_ids.h.jinja b/autogen/templates/can_board_ids/can_board_ids.h.jinja similarity index 86% rename from libraries/codegen/templates/can_board_ids.h.jinja rename to autogen/templates/can_board_ids/can_board_ids.h.jinja index 2dbab0302..131a75d80 100644 --- a/libraries/codegen/templates/can_board_ids.h.jinja +++ b/autogen/templates/can_board_ids/can_board_ids.h.jinja @@ -1,6 +1,3 @@ -{% set boards = data["Boards"] -%} -{% set messages = data["Messages"] -%} - #pragma once #include diff --git a/libraries/codegen/templates/system_can.dbc.jinja b/autogen/templates/can_board_ids/system_can.dbc.jinja similarity index 85% rename from libraries/codegen/templates/system_can.dbc.jinja rename to autogen/templates/can_board_ids/system_can.dbc.jinja index 74a941a05..64f47faa4 100644 --- a/libraries/codegen/templates/system_can.dbc.jinja +++ b/autogen/templates/can_board_ids/system_can.dbc.jinja @@ -1,6 +1,3 @@ -{% set boards = data["Boards"] -%} -{% set messages = data["Messages"] -%} - NS_ : NS_DESC_ CM_ @@ -34,7 +31,7 @@ NS_ : BS_: BU_: -{%- for board in boards %} {{board|upper}} {%- endfor %} +{%- for board in boards %} {{ board | upper }} {%- endfor %} {% for message in messages -%} diff --git a/scons/template/README.md b/autogen/templates/new/README.md.jinja similarity index 97% rename from scons/template/README.md rename to autogen/templates/new/README.md.jinja index d54852911..aa459c4d6 100644 --- a/scons/template/README.md +++ b/autogen/templates/new/README.md.jinja @@ -12,4 +12,5 @@ A README for a board project (powering a hardware board, e.g. power distribution - How does it fit into the overall system? - How does it work? (architectural overview, e.g. what each module's purpose is or how data flows through the firmware) --> -# \ No newline at end of file +# {{ proj_name }} + diff --git a/autogen/templates/new/config.json.jinja b/autogen/templates/new/config.json.jinja new file mode 100644 index 000000000..fc92c9761 --- /dev/null +++ b/autogen/templates/new/config.json.jinja @@ -0,0 +1,8 @@ +{ + "libs": [ +{%- for lib in libs %} + "{{ lib }}"{{ "," if not loop.last }} +{%- endfor %} + ], + "can": false +} diff --git a/autogen/templates/new/inc/inc.h.jinja b/autogen/templates/new/inc/inc.h.jinja new file mode 100644 index 000000000..e69de29bb diff --git a/scons/template/main.c b/autogen/templates/new/src/main.c.jinja similarity index 87% rename from scons/template/main.c rename to autogen/templates/new/src/main.c.jinja index edca49486..6715f27c4 100644 --- a/scons/template/main.c +++ b/autogen/templates/new/src/main.c.jinja @@ -15,7 +15,7 @@ void run_slow_cycle() {} int main() { tasks_init(); log_init(); - LOG_DEBUG("Welcome to TEST!"); + LOG_DEBUG("Welcome to {{ proj_name }}!"); init_master_task(); diff --git a/autogen/templates/new/test/test.c.jinja b/autogen/templates/new/test/test.c.jinja new file mode 100644 index 000000000..b48e41fb1 --- /dev/null +++ b/autogen/templates/new/test/test.c.jinja @@ -0,0 +1,11 @@ +#include + +#include "unity.h" + +void setup_test(void) {} + +void teardown_test(void) {} + +void test_example(void) { + TEST_ASSERT_TRUE(true); +} diff --git a/autogen/templates/new_py/__init__.py.jinja b/autogen/templates/new_py/__init__.py.jinja new file mode 100644 index 000000000..e69de29bb diff --git a/autogen/templates/new_py/config.json.jinja b/autogen/templates/new_py/config.json.jinja new file mode 100644 index 000000000..65da8ebcb --- /dev/null +++ b/autogen/templates/new_py/config.json.jinja @@ -0,0 +1,3 @@ +{ + "can": false +} diff --git a/autogen/templates/new_py/main.py.jinja b/autogen/templates/new_py/main.py.jinja new file mode 100644 index 000000000..de8a96bc1 --- /dev/null +++ b/autogen/templates/new_py/main.py.jinja @@ -0,0 +1,7 @@ + +def main(): + print("Welcome to {{ proj_name }}") + +if __name__ == "__main__": + main() + diff --git a/autogen/templates/new_task/inc/{{task_name}}.h.jinja b/autogen/templates/new_task/inc/{{task_name}}.h.jinja new file mode 100644 index 000000000..fbf018d4b --- /dev/null +++ b/autogen/templates/new_task/inc/{{task_name}}.h.jinja @@ -0,0 +1,14 @@ +#include "status.h" + +/* + * Name: {{ task_name }} + * Description: What this task does and to use it + * Author: + * Date: +*/ + +void run_{{ task_name }}_cycle(); +void run_{{ task_name }}_fast_cycle(); +void run_{{ task_name }}_medium_cycle(); +void run_{{ task_name }}_slow_cycle(); +StatusCode init_{{ task_name }}(); diff --git a/autogen/templates/new_task/src/{{task_name}}.c.jinja b/autogen/templates/new_task/src/{{task_name}}.c.jinja new file mode 100644 index 000000000..9c49f2323 --- /dev/null +++ b/autogen/templates/new_task/src/{{task_name}}.c.jinja @@ -0,0 +1,42 @@ +#include "log.h" +#include "tasks.h" +#include "{{ task_name }}.h" + +static SemaphoreHandle_t s_{{ task_name }}_sem_handle; +static StaticSemaphore_t s_{{ task_name }}_sem; + +void run_{{ task_name }}_cycle() { + BaseType_t ret = xSemaphoreGive(s_{{ task_name }}_sem_handle); + + if (ret == pdFALSE) { + return STATUS_CODE_INTERNAL_ERROR; + } + + return STATUS_CODE_OK; +} + +TASK({{ task_name }}, TASK_MIN_STACK_SIZE) { + int counter = 0; + while (true) + { + xSemaphoreTake(s_{{ task_name }}_sem_handle, portMAX_DELAY); + counter++; + run_{{ task_name }}_fast_cycle(); + if ((counter % 10) == 0) + run_{{ task_name }}_medium_cycle(); + if ((counter % 100) == 0) + run_{{ task_name }}_slow_cycle(); + send_task_end(); + } +} + +void run_{{ task_name }}_fast_cycle() {} + +void run_{{ task_name }}_medium_cycle() {} + +void run_{{ task_name }}_slow_cycle() {} + +StatusCode init_{{ task_name }}() { + status_ok_or_return(tasks_init_task({{ task_name }}, TASK_PRIORITY(2), NULL)); + return STATUS_CODE_OK; +} diff --git a/autogen/templates/python_can/messages.py.jinja b/autogen/templates/python_can/messages.py.jinja new file mode 100644 index 000000000..ffdac4243 --- /dev/null +++ b/autogen/templates/python_can/messages.py.jinja @@ -0,0 +1,35 @@ +import struct + +# Board and Message ids +{% for board in boards -%} +SYSTEM_CAN_DEVICE_{{ board | upper }} = {{ loop.index0 }} +{% endfor %} +NUM_SYSTEM_CAN_DEVICES = {{ boards | length }} + +{% for message in messages -%} +SYSTEM_CAN_MESSAGE_{{ message.sender | upper }}_{{ message.name | upper }} = {{ message.id * 32 + boards.index(message.sender) }} # ({{ message.id }} << 5) + SYSTEM_CAN_DEVICE_{{ message.sender | upper }} +{% endfor %} + +def pack(num, size): + if isinstance(num, float) and (size == 32): + return struct.pack("f", num).hex() + elif (size == 32): + return struct.pack("i", num).hex() + elif (size == 16): + return struct.pack("h", num).hex() + elif (size == 8): + return struct.pack("b", num).hex() + +{% for message in messages %} +def {{ message.sender }}_{{ message.name }}( + {%- for signal in message.signals -%} + {{- signal.name -}}{{- ", " if not loop.last -}} + {%- endfor -%} +): + data = + {%- for signal in message.signals -%} + pack({{ signal.name }}, {{ signal.length }}) + {{- (' + ' if loop.index % 2 else ' + \\\n\t\t') if not loop.last }} + {%- endfor %} + return f"{SYSTEM_CAN_MESSAGE_{{ message.sender | upper }}_{{ message.name | upper }}:3>x}#{data}" +{% endfor %} diff --git a/libraries/unity/auto/test_runner.c.jinja b/autogen/templates/test/{{filename}}.c.jinja similarity index 77% rename from libraries/unity/auto/test_runner.c.jinja rename to autogen/templates/test/{{filename}}.c.jinja index 07331c496..62841ee72 100644 --- a/libraries/unity/auto/test_runner.c.jinja +++ b/autogen/templates/test/{{filename}}.c.jinja @@ -1,6 +1,6 @@ /* AUTOGENERATED FILE. DO NOT EDIT. */ /*=======Automagically Detected Files To Include=====*/ -{% if data.has_in_task_test -%} +{% if in_task_tests -%} #include "FreeRTOS.h" #include "tasks.h" {% endif -%} @@ -9,7 +9,7 @@ /*=======External Functions This Runner Calls=====*/ extern void setup_test(void); -{%- for test in data.tests %} +{%- for test in in_task_tests + pre_task_tests %} extern void {{test.name}}(void); {%- endfor %} @@ -22,7 +22,7 @@ static void run_test(UnityTestFunction func, const char *name, UNITY_LINE_TYPE l UNITY_CLR_DETAILS(); if (TEST_PROTECT()) { -{%- if data.has_in_task_test %} +{%- if in_task_tests %} vTaskSuspendAll(); setup_test(); xTaskResumeAll(); @@ -37,16 +37,17 @@ static void run_test(UnityTestFunction func, const char *name, UNITY_LINE_TYPE l /*=======PRE TASK TESTS=====*/ void test_pre_task(void) { -{%- for test in data.tests | selectattr("type", "eq", "TEST_PRE_TASK") %} +{%- for test in pre_task_tests %} run_test({{test.name}}, "{{test.name}}", {{test.line}}); {%- endfor %} } -{% if data.has_in_task_test -%} +{% if in_task_tests -%} /*=======IN TASK TESTS=====*/ TASK(test_task, TASK_STACK_1024) { + UnityBegin("{{filename}}"); - {% for test in data.tests | selectattr("type", "eq", "TEST_IN_TASK") %} + {% for test in in_task_tests %} run_test({{test.name}}, "{{test.name}}", {{test.line}}); {% endfor %} @@ -57,16 +58,11 @@ TASK(test_task, TASK_STACK_1024) { /*=======MAIN=====*/ int main() { setup_test(); - - UnityBegin("{{data.filename}}"); test_pre_task(); -{%- if data.has_in_task_test %} +{%- if in_task_tests %} tasks_init(); tasks_init_task(test_task, configMAX_PRIORITIES - 1, NULL); tasks_start(); -{%- else %} - exit(UnityEnd()); {%- endif %} - return 0; } diff --git a/autogen/test.py b/autogen/test.py new file mode 100644 index 000000000..f92a6582c --- /dev/null +++ b/autogen/test.py @@ -0,0 +1,32 @@ +import re +from pathlib import Path + + +def get_data(args): + output_file = Path(args.output) + print(output_file) + input_file = Path(*output_file.parts[3:]) + print(input_file) + # set args.output to output_dir + args.output = str(output_file.parent) + + tests = {"TEST_PRE_TASK": [], "TEST_IN_TASK": []} + default_type = "TEST_PRE_TASK" + content = input_file.read_text().splitlines() + for lineNum, line in enumerate(content): + res = re.search('^\s*void \s*(test_([A-z0-9]|_)*)\s*\((void)?\)', line) + if res != None: + test_type = content[lineNum - 1].strip() if (lineNum > 0) else '' + # append to to correct test type, default to default_type if the type is not valid + tests.get(test_type, tests[default_type]).append({ + "name": res.group(1), + "line": lineNum + 1, + }) + + # raise error if there are no tests + if all([len(tests) == 0 for test in tests.values()]): + raise Exception(f"No tests specified in {input_file}") + + return {"filename": output_file.stem, + "pre_task_tests": tests["TEST_PRE_TASK"], + "in_task_tests": tests["TEST_IN_TASK"]} diff --git a/build_system.md b/build_system.md index 082e9525f..8b27c2f98 100644 --- a/build_system.md +++ b/build_system.md @@ -78,8 +78,8 @@ Commands: clean Delete the `build` directory. -Target: -targest can be specified with an option. +Targets: +targets can be specified with an option. --project= specify the target as a project with `name` diff --git a/libraries/codegen/boards/babydriver.yaml b/can/boards/babydriver.yaml similarity index 100% rename from libraries/codegen/boards/babydriver.yaml rename to can/boards/babydriver.yaml diff --git a/libraries/codegen/boards/bms_carrier.yaml b/can/boards/bms_carrier.yaml similarity index 100% rename from libraries/codegen/boards/bms_carrier.yaml rename to can/boards/bms_carrier.yaml diff --git a/libraries/codegen/boards/can_communication.yaml b/can/boards/can_communication.yaml similarity index 100% rename from libraries/codegen/boards/can_communication.yaml rename to can/boards/can_communication.yaml diff --git a/libraries/codegen/boards/can_debug.yaml b/can/boards/can_debug.yaml similarity index 100% rename from libraries/codegen/boards/can_debug.yaml rename to can/boards/can_debug.yaml diff --git a/libraries/codegen/boards/centre_console.yaml b/can/boards/centre_console.yaml similarity index 100% rename from libraries/codegen/boards/centre_console.yaml rename to can/boards/centre_console.yaml diff --git a/libraries/codegen/boards/charger.yaml b/can/boards/charger.yaml similarity index 100% rename from libraries/codegen/boards/charger.yaml rename to can/boards/charger.yaml diff --git a/libraries/codegen/boards/motor_controller.yaml b/can/boards/motor_controller.yaml similarity index 100% rename from libraries/codegen/boards/motor_controller.yaml rename to can/boards/motor_controller.yaml diff --git a/libraries/codegen/boards/new_can.yaml b/can/boards/new_can.yaml similarity index 100% rename from libraries/codegen/boards/new_can.yaml rename to can/boards/new_can.yaml diff --git a/libraries/codegen/boards/pedal.yaml b/can/boards/pedal.yaml similarity index 100% rename from libraries/codegen/boards/pedal.yaml rename to can/boards/pedal.yaml diff --git a/libraries/codegen/boards/power_distribution.yaml b/can/boards/power_distribution.yaml similarity index 100% rename from libraries/codegen/boards/power_distribution.yaml rename to can/boards/power_distribution.yaml diff --git a/libraries/codegen/boards/power_select.yaml b/can/boards/power_select.yaml similarity index 100% rename from libraries/codegen/boards/power_select.yaml rename to can/boards/power_select.yaml diff --git a/libraries/codegen/boards/solar_sense.yaml b/can/boards/solar_sense.yaml similarity index 100% rename from libraries/codegen/boards/solar_sense.yaml rename to can/boards/solar_sense.yaml diff --git a/libraries/codegen/boards/steering.yaml b/can/boards/steering.yaml similarity index 100% rename from libraries/codegen/boards/steering.yaml rename to can/boards/steering.yaml diff --git a/libraries/codegen/boards/telemetry.yaml b/can/boards/telemetry.yaml similarity index 100% rename from libraries/codegen/boards/telemetry.yaml rename to can/boards/telemetry.yaml diff --git a/libraries/codegen/boards/uv_cutoff.yaml b/can/boards/uv_cutoff.yaml similarity index 100% rename from libraries/codegen/boards/uv_cutoff.yaml rename to can/boards/uv_cutoff.yaml diff --git a/libraries/codegen/config.json b/libraries/codegen/config.json deleted file mode 100644 index 68c4a6bcc..000000000 --- a/libraries/codegen/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "no_lint": true -} \ No newline at end of file diff --git a/libraries/codegen/templates/can.py.jinja b/libraries/codegen/templates/can.py.jinja deleted file mode 100644 index d09b2b3eb..000000000 --- a/libraries/codegen/templates/can.py.jinja +++ /dev/null @@ -1,57 +0,0 @@ -{% set boards = data["Boards"] -%} -{% set messages = data["Messages"] -%} - -import struct -import subprocess -import time -import threading - -{% for message in messages -%} -SYSTEM_CAN_MESSAGE_{{ message["sender"] | upper }}_{{ message.name | upper }} = {{ message.id * 0x20 + + boards.index(message.sender)}} -{% endfor -%} - -# Send single message -def send_message(id, data): - # id: int -> hex, data: str - cmd = f"cansend vcan0 {hex(id)[2:].zfill(3)}#{data}" - subprocess.run(cmd, shell=True) - -def pack(num, size): - if isinstance(num, float) and (size == 32): - return struct.pack("f", num).hex() - elif (size == 32): - return struct.pack("i", num).hex() - elif (size == 16): - return struct.pack("h", num).hex() - elif (size == 8): - return struct.pack("b", num).hex() - -{%- for message in messages %} -def send_{{ message["sender"] }}_{{ message.name }}( - {%- for signal in message.signals %} - {{- signal.name -}}{{- ", " if not loop.last -}} - {%- endfor -%} -): - send_message(SYSTEM_CAN_MESSAGE_{{ message["sender"] | upper }}_{{ message.name | upper }}, - {%- for signal in message.signals %} - pack({{ signal.name }}, {{ signal["length"] }}){{ ' + ' if not loop.last }} - {%- endfor -%} - ) -{% endfor %} - -def repeat(repeat_period, send_device_message, *args): - def threading_repeat(kill_process): - while not kill_process.is_set(): - send_device_message(*args) - time.sleep(repeat_period) - - kill_process = threading.Event() - - process_name = threading.Thread(target=threading_repeat, args=(kill_process,)) - process_name.start() - - return process_name, kill_process - -def end_repeat(send_device_message_process, kill_process): - kill_process.set() - send_device_message_process.join() diff --git a/libraries/unity/auto/generate_test_runner.py b/libraries/unity/auto/generate_test_runner.py deleted file mode 100644 index 1c43ba8bb..000000000 --- a/libraries/unity/auto/generate_test_runner.py +++ /dev/null @@ -1,54 +0,0 @@ -from sys import argv -import re -import jinja2 -from pathlib import Path - - -def get_tests(input): - ''' - Get all tests in file, return a list of tests, each test is - { - "name": , name of the test - "line": , the line number of the test - "type": , "TEST_PRE_TASK" or "TEST_IN_TASK" - } - ''' - tests = [] - test_types = {"TEST_PRE_TASK", "TEST_IN_TASK"} - default_type = "TEST_PRE_TASK" - for lineNum, line in enumerate(input): - res = re.search('^\s*void \s*(test_([A-z0-9]|_)*)\s*\((void)?\)', line) - if res != None: - test_type = input[lineNum - 1].strip() if (lineNum > 0) else '' - tests.append({ - "name": res.group(1), - "line": lineNum + 1, - "type": test_type if (test_type in test_types) else default_type - }) - return tests - - -def main(input_file, output_file): - current_dir = Path(__file__).parent - template_loader = jinja2.FileSystemLoader(searchpath=current_dir) - env = jinja2.Environment(loader=template_loader) - - with open(input_file, 'r') as input: - content = input.readlines() - - tests = get_tests(content) - data = { - "tests": tests, - "has_in_task_test": any([test["type"] == "TEST_IN_TASK" for test in tests]), - "filename": input_file, - } - - if len(tests) == 0: - raise Exception(f"No tests specified in {input_file}") - - with open(output_file, 'w') as output: - res = env.get_template("test_runner.c.jinja").render(data=data) - output.write(res) - -if __name__ == "__main__": - main(argv[2], argv[3]) diff --git a/libraries/unity/auto/generate_test_runner.rb b/libraries/unity/auto/generate_test_runner.rb deleted file mode 100644 index 072a6aff2..000000000 --- a/libraries/unity/auto/generate_test_runner.rb +++ /dev/null @@ -1,518 +0,0 @@ -# ========================================== -# Unity Project - A Test Framework for C -# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams -# [Released under MIT License. Please refer to license.txt for details] -# ========================================== - -class UnityTestRunnerGenerator - def initialize(options = nil) - @options = UnityTestRunnerGenerator.default_options - case options - when NilClass - @options - when String - @options.merge!(UnityTestRunnerGenerator.grab_config(options)) - when Hash - # Check if some of these have been specified - @options[:has_setup] = !options[:setup_name].nil? - @options[:has_teardown] = !options[:teardown_name].nil? - @options[:has_suite_setup] = !options[:suite_setup].nil? - @options[:has_suite_teardown] = !options[:suite_teardown].nil? - @options.merge!(options) - else - raise 'If you specify arguments, it should be a filename or a hash of options' - end - require_relative 'type_sanitizer' - end - - def self.default_options - { - includes: [], - defines: [], - plugins: [], - framework: :unity, - test_prefix: 'test|spec|should', - mock_prefix: 'Mock', - mock_suffix: '', - setup_name: 'setUp', - teardown_name: 'tearDown', - test_reset_name: 'resetTest', - test_verify_name: 'verifyTest', - main_name: 'main', # set to :auto to automatically generate each time - main_export_decl: '', - cmdline_args: false, - omit_begin_end: false, - use_param_tests: false, - include_extensions: '(?:hpp|hh|H|h)', - source_extensions: '(?:cpp|cc|ino|C|c)' - } - end - - def self.grab_config(config_file) - options = default_options - unless config_file.nil? || config_file.empty? - require 'yaml' - yaml_guts = YAML.load_file(config_file) - options.merge!(yaml_guts[:unity] || yaml_guts[:cmock]) - raise "No :unity or :cmock section found in #{config_file}" unless options - end - options - end - - def run(input_file, output_file, options = nil) - @options.merge!(options) unless options.nil? - - # pull required data from source file - source = File.read(input_file) - source = source.force_encoding('ISO-8859-1').encode('utf-8', replace: nil) - tests = find_tests(source) - headers = find_includes(source) - testfile_includes = (headers[:local] + headers[:system]) - used_mocks = find_mocks(testfile_includes) - testfile_includes = (testfile_includes - used_mocks) - testfile_includes.delete_if { |inc| inc =~ /(unity|cmock)/ } - find_setup_and_teardown(source) - - # build runner file - generate(input_file, output_file, tests, used_mocks, testfile_includes) - - # determine which files were used to return them - all_files_used = [input_file, output_file] - all_files_used += testfile_includes.map { |filename| filename + '.c' } unless testfile_includes.empty? - all_files_used += @options[:includes] unless @options[:includes].empty? - all_files_used += headers[:linkonly] unless headers[:linkonly].empty? - all_files_used.uniq - end - - def generate(input_file, output_file, tests, used_mocks, testfile_includes) - File.open(output_file, 'w') do |output| - create_header(output, used_mocks, testfile_includes) - create_externs(output, tests, used_mocks) - create_mock_management(output, used_mocks) - create_setup(output) - create_teardown(output) - create_suite_setup(output) - create_suite_teardown(output) - create_reset(output) - create_run_test(output) unless tests.empty? - create_args_wrappers(output, tests) - create_main(output, input_file, tests, used_mocks) - end - - return unless @options[:header_file] && !@options[:header_file].empty? - - File.open(@options[:header_file], 'w') do |output| - create_h_file(output, @options[:header_file], tests, testfile_includes, used_mocks) - end - end - - def find_tests(source) - tests_and_line_numbers = [] - - # contains characters which will be substituted from within strings, doing - # this prevents these characters from interfering with scrubbers - # @ is not a valid C character, so there should be no clashes with files genuinely containing these markers - substring_subs = { '{' => '@co@', '}' => '@cc@', ';' => '@ss@', '/' => '@fs@' } - substring_re = Regexp.union(substring_subs.keys) - substring_unsubs = substring_subs.invert # the inverse map will be used to fix the strings afterwords - substring_unsubs['@quote@'] = '\\"' - substring_unsubs['@apos@'] = '\\\'' - substring_unre = Regexp.union(substring_unsubs.keys) - source_scrubbed = source.clone - source_scrubbed = source_scrubbed.gsub(/\\"/, '@quote@') # hide escaped quotes to allow capture of the full string/char - source_scrubbed = source_scrubbed.gsub(/\\'/, '@apos@') # hide escaped apostrophes to allow capture of the full string/char - source_scrubbed = source_scrubbed.gsub(/("[^"\n]*")|('[^'\n]*')/) { |s| s.gsub(substring_re, substring_subs) } # temporarily hide problematic characters within strings - source_scrubbed = source_scrubbed.gsub(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks - source_scrubbed = source_scrubbed.gsub(/\/\*.*?\*\//m, '') # remove block comments - source_scrubbed = source_scrubbed.gsub(/\/\/.*$/, '') # remove line comments (all that remain) - lines = source_scrubbed.split(/(^\s*\#.*$) | (;|\{|\}) /x) # Treat preprocessor directives as a logical line. Match ;, {, and } as end of lines - .map { |line| line.gsub(substring_unre, substring_unsubs) } # unhide the problematic characters previously removed - - lines.each_with_index do |line, _index| - # find tests - next unless line =~ /^((?:\s*(?:TEST_CASE|TEST_RANGE)\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/m - - arguments = Regexp.last_match(1) - name = Regexp.last_match(2) - call = Regexp.last_match(3) - params = Regexp.last_match(4) - args = nil - - if @options[:use_param_tests] && !arguments.empty? - args = [] - arguments.scan(/\s*TEST_CASE\s*\((.*)\)\s*$/) { |a| args << a[0] } - - arguments.scan(/\s*TEST_RANGE\s*\((.*)\)\s*$/).flatten.each do |range_str| - args += range_str.scan(/\[\s*(-?\d+.?\d*),\s*(-?\d+.?\d*),\s*(-?\d+.?\d*)\s*\]/).map do |arg_values_str| - arg_values_str.map do |arg_value_str| - arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i - end - end.map do |arg_values| - (arg_values[0]..arg_values[1]).step(arg_values[2]).to_a - end.reduce do |result, arg_range_expanded| - result.product(arg_range_expanded) - end.map do |arg_combinations| - arg_combinations.flatten.join(', ') - end - end - end - - tests_and_line_numbers << { test: name, args: args, call: call, params: params, line_number: 0 } - end - - tests_and_line_numbers.uniq! { |v| v[:test] } - - # determine line numbers and create tests to run - source_lines = source.split("\n") - source_index = 0 - tests_and_line_numbers.size.times do |i| - source_lines[source_index..-1].each_with_index do |line, index| - next unless line =~ /\s+#{tests_and_line_numbers[i][:test]}(?:\s|\()/ - - source_index += index - tests_and_line_numbers[i][:line_number] = source_index + 1 - break - end - end - - tests_and_line_numbers - end - - def find_includes(source) - # remove comments (block and line, in three steps to ensure correct precedence) - source.gsub!(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks - source.gsub!(/\/\*.*?\*\//m, '') # remove block comments - source.gsub!(/\/\/.*$/, '') # remove line comments (all that remain) - - # parse out includes - includes = { - local: source.scan(/^\s*#include\s+\"\s*(.+\.#{@options[:include_extensions]})\s*\"/).flatten, - system: source.scan(/^\s*#include\s+<\s*(.+)\s*>/).flatten.map { |inc| "<#{inc}>" }, - linkonly: source.scan(/^TEST_FILE\(\s*\"\s*(.+\.#{@options[:source_extensions]})\s*\"/).flatten - } - includes - end - - def find_mocks(includes) - mock_headers = [] - includes.each do |include_path| - include_file = File.basename(include_path) - mock_headers << include_path if include_file =~ /^#{@options[:mock_prefix]}.*#{@options[:mock_suffix]}\.h$/i - end - mock_headers - end - - def find_setup_and_teardown(source) - @options[:has_setup] = source =~ /void\s+#{@options[:setup_name]}\s*\(/ - @options[:has_teardown] = source =~ /void\s+#{@options[:teardown_name]}\s*\(/ - @options[:has_suite_setup] ||= (source =~ /void\s+suiteSetUp\s*\(/) - @options[:has_suite_teardown] ||= (source =~ /int\s+suiteTearDown\s*\(int\s+([a-zA-Z0-9_])+\s*\)/) - end - - def create_header(output, mocks, testfile_includes = []) - output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */') - output.puts("\n/*=======Automagically Detected Files To Include=====*/") - output.puts('extern "C" {') if @options[:externcincludes] - output.puts("#include \"#{@options[:framework]}.h\"") - output.puts('#include "cmock.h"') unless mocks.empty? - output.puts('}') if @options[:externcincludes] - if @options[:defines] && !@options[:defines].empty? - @options[:defines].each { |d| output.puts("#ifndef #{d}\n#define #{d}\n#endif /* #{d} */") } - end - if @options[:header_file] && !@options[:header_file].empty? - output.puts("#include \"#{File.basename(@options[:header_file])}\"") - else - @options[:includes].flatten.uniq.compact.each do |inc| - output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") - end - testfile_includes.each do |inc| - output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") - end - end - output.puts('extern "C" {') if @options[:externcincludes] - mocks.each do |mock| - output.puts("#include \"#{mock}\"") - end - output.puts('}') if @options[:externcincludes] - output.puts('#include "CException.h"') if @options[:plugins].include?(:cexception) - - return unless @options[:enforce_strict_ordering] - - output.puts('') - output.puts('int GlobalExpectCount;') - output.puts('int GlobalVerifyOrder;') - output.puts('char* GlobalOrderError;') - end - - def create_externs(output, tests, _mocks) - output.puts("\n/*=======External Functions This Runner Calls=====*/") - output.puts("extern void #{@options[:setup_name]}(void);") - output.puts("extern void #{@options[:teardown_name]}(void);") - output.puts("\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif") if @options[:externc] - tests.each do |test| - output.puts("extern void #{test[:test]}(#{test[:call] || 'void'});") - end - output.puts("#ifdef __cplusplus\n}\n#endif") if @options[:externc] - output.puts('') - end - - def create_mock_management(output, mock_headers) - output.puts("\n/*=======Mock Management=====*/") - output.puts('static void CMock_Init(void)') - output.puts('{') - - if @options[:enforce_strict_ordering] - output.puts(' GlobalExpectCount = 0;') - output.puts(' GlobalVerifyOrder = 0;') - output.puts(' GlobalOrderError = NULL;') - end - - mocks = mock_headers.map { |mock| File.basename(mock, '.*') } - mocks.each do |mock| - mock_clean = TypeSanitizer.sanitize_c_identifier(mock) - output.puts(" #{mock_clean}_Init();") - end - output.puts("}\n") - - output.puts('static void CMock_Verify(void)') - output.puts('{') - mocks.each do |mock| - mock_clean = TypeSanitizer.sanitize_c_identifier(mock) - output.puts(" #{mock_clean}_Verify();") - end - output.puts("}\n") - - output.puts('static void CMock_Destroy(void)') - output.puts('{') - mocks.each do |mock| - mock_clean = TypeSanitizer.sanitize_c_identifier(mock) - output.puts(" #{mock_clean}_Destroy();") - end - output.puts("}\n") - end - - def create_setup(output) - return if @options[:has_setup] - - output.puts("\n/*=======Setup (stub)=====*/") - output.puts("void #{@options[:setup_name]}(void) {}") - end - - def create_teardown(output) - return if @options[:has_teardown] - - output.puts("\n/*=======Teardown (stub)=====*/") - output.puts("void #{@options[:teardown_name]}(void) {}") - end - - def create_suite_setup(output) - return if @options[:suite_setup].nil? - - output.puts("\n/*=======Suite Setup=====*/") - output.puts('void suiteSetUp(void)') - output.puts('{') - output.puts(@options[:suite_setup]) - output.puts('}') - end - - def create_suite_teardown(output) - return if @options[:suite_teardown].nil? - - output.puts("\n/*=======Suite Teardown=====*/") - output.puts('int suiteTearDown(int num_failures)') - output.puts('{') - output.puts(@options[:suite_teardown]) - output.puts('}') - end - - def create_reset(output) - output.puts("\n/*=======Test Reset Options=====*/") - output.puts("void #{@options[:test_reset_name]}(void);") - output.puts("void #{@options[:test_reset_name]}(void)") - output.puts('{') - output.puts(" #{@options[:teardown_name]}();") - output.puts(' CMock_Verify();') - output.puts(' CMock_Destroy();') - output.puts(' CMock_Init();') - output.puts(" #{@options[:setup_name]}();") - output.puts('}') - output.puts("void #{@options[:test_verify_name]}(void);") - output.puts("void #{@options[:test_verify_name]}(void)") - output.puts('{') - output.puts(' CMock_Verify();') - output.puts('}') - end - - def create_run_test(output) - require 'erb' - template = ERB.new(File.read(File.join(__dir__, 'run_test.erb')), nil, '<>') - output.puts("\n" + template.result(binding)) - end - - def create_args_wrappers(output, tests) - return unless @options[:use_param_tests] - - output.puts("\n/*=======Parameterized Test Wrappers=====*/") - tests.each do |test| - next if test[:args].nil? || test[:args].empty? - - test[:args].each.with_index(1) do |args, idx| - output.puts("static void runner_args#{idx}_#{test[:test]}(void)") - output.puts('{') - output.puts(" #{test[:test]}(#{args});") - output.puts("}\n") - end - end - end - - def create_main(output, filename, tests, used_mocks) - output.puts("\n/*=======MAIN=====*/") - main_name = @options[:main_name].to_sym == :auto ? "main_#{filename.gsub('.c', '')}" : (@options[:main_name]).to_s - if @options[:cmdline_args] - if main_name != 'main' - output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv);") - end - output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv)") - output.puts('{') - output.puts(' int parse_status = UnityParseOptions(argc, argv);') - output.puts(' if (parse_status != 0)') - output.puts(' {') - output.puts(' if (parse_status < 0)') - output.puts(' {') - output.puts(" UnityPrint(\"#{filename.gsub('.c', '')}.\");") - output.puts(' UNITY_PRINT_EOL();') - tests.each do |test| - if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty? - output.puts(" UnityPrint(\" #{test[:test]}\");") - output.puts(' UNITY_PRINT_EOL();') - else - test[:args].each do |args| - output.puts(" UnityPrint(\" #{test[:test]}(#{args})\");") - output.puts(' UNITY_PRINT_EOL();') - end - end - end - output.puts(' return 0;') - output.puts(' }') - output.puts(' return parse_status;') - output.puts(' }') - else - main_return = @options[:omit_begin_end] ? 'void' : 'int' - if main_name != 'main' - output.puts("#{@options[:main_export_decl]} #{main_return} #{main_name}(void);") - end - output.puts("#{main_return} #{main_name}(void)") - output.puts('{') - end - output.puts(' suiteSetUp();') if @options[:has_suite_setup] - if @options[:omit_begin_end] - output.puts(" UnitySetTestFile(\"#{filename.gsub(/\\/, '\\\\\\')}\");") - else - output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");") - end - tests.each do |test| - if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty? - output.puts(" run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});") - else - test[:args].each.with_index(1) do |args, idx| - wrapper = "runner_args#{idx}_#{test[:test]}" - testname = "#{test[:test]}(#{args})".dump - output.puts(" run_test(#{wrapper}, #{testname}, #{test[:line_number]});") - end - end - end - output.puts - output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty? - if @options[:has_suite_teardown] - if @options[:omit_begin_end] - output.puts(' (void) suite_teardown(0);') - else - output.puts(' return suiteTearDown(UnityEnd());') - end - else - output.puts(' return UnityEnd();') unless @options[:omit_begin_end] - end - output.puts('}') - end - - def create_h_file(output, filename, tests, testfile_includes, used_mocks) - filename = File.basename(filename).gsub(/[-\/\\\.\,\s]/, '_').upcase - output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */') - output.puts("#ifndef _#{filename}") - output.puts("#define _#{filename}\n\n") - output.puts("#include \"#{@options[:framework]}.h\"") - output.puts('#include "cmock.h"') unless used_mocks.empty? - @options[:includes].flatten.uniq.compact.each do |inc| - output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") - end - testfile_includes.each do |inc| - output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") - end - output.puts "\n" - tests.each do |test| - if test[:params].nil? || test[:params].empty? - output.puts("void #{test[:test]}(void);") - else - output.puts("void #{test[:test]}(#{test[:params]});") - end - end - output.puts("#endif\n\n") - end - end - - if $0 == __FILE__ - options = { includes: [] } - - # parse out all the options first (these will all be removed as we go) - ARGV.reject! do |arg| - case arg - when '-cexception' - options[:plugins] = [:cexception] - true - when '-externcincludes' - options[:externcincludes] = true - true - when /\.*\.ya?ml$/ - options = UnityTestRunnerGenerator.grab_config(arg) - true - when /--(\w+)=\"?(.*)\"?/ - options[Regexp.last_match(1).to_sym] = Regexp.last_match(2) - true - when /\.*\.(?:hpp|hh|H|h)$/ - options[:includes] << arg - true - else false - end - end - - # make sure there is at least one parameter left (the input file) - unless ARGV[0] - puts ["\nusage: ruby #{__FILE__} (files) (options) input_test_file (output)", - "\n input_test_file - this is the C file you want to create a runner for", - ' output - this is the name of the runner file to generate', - ' defaults to (input_test_file)_Runner', - ' files:', - ' *.yml / *.yaml - loads configuration from here in :unity or :cmock', - ' *.h - header files are added as #includes in runner', - ' options:', - ' -cexception - include cexception support', - ' -externc - add extern "C" for cpp support', - ' --setup_name="" - redefine setUp func name to something else', - ' --teardown_name="" - redefine tearDown func name to something else', - ' --main_name="" - redefine main func name to something else', - ' --test_prefix="" - redefine test prefix from default test|spec|should', - ' --test_reset_name="" - redefine resetTest func name to something else', - ' --test_verify_name="" - redefine verifyTest func name to something else', - ' --suite_setup="" - code to execute for setup of entire suite', - ' --suite_teardown="" - code to execute for teardown of entire suite', - ' --use_param_tests=1 - enable parameterized tests (disabled by default)', - ' --omit_begin_end=1 - omit calls to UnityBegin and UnityEnd (disabled by default)', - ' --header_file="" - path/name of test header file to generate too'].join("\n") - exit 1 - end - - # create the default test runner name if not specified - ARGV[1] = ARGV[0].gsub('.c', '_Runner.c') unless ARGV[1] - - UnityTestRunnerGenerator.new(options).run(ARGV[0], ARGV[1]) - end diff --git a/libraries/unity/auto/run_test.erb b/libraries/unity/auto/run_test.erb deleted file mode 100644 index f91b56691..000000000 --- a/libraries/unity/auto/run_test.erb +++ /dev/null @@ -1,37 +0,0 @@ -/*=======Test Runner Used To Run Each Test=====*/ -static void run_test(UnityTestFunction func, const char* name, UNITY_LINE_TYPE line_num) -{ - Unity.CurrentTestName = name; - Unity.CurrentTestLineNumber = line_num; -#ifdef UNITY_USE_COMMAND_LINE_ARGS - if (!UnityTestMatches()) - return; -#endif - Unity.NumberOfTests++; - UNITY_CLR_DETAILS(); - UNITY_EXEC_TIME_START(); - CMock_Init(); - if (TEST_PROTECT()) - { -<% if @options[:plugins].include?(:cexception) %> - CEXCEPTION_T e; - Try { - <%= @options[:setup_name] %>(); - func(); - } Catch(e) { - TEST_ASSERT_EQUAL_HEX32_MESSAGE(CEXCEPTION_NONE, e, "Unhandled Exception!"); - } -<% else %> - <%= @options[:setup_name] %>(); - func(); -<% end %> - } - if (TEST_PROTECT()) - { - <%= @options[:teardown_name] %>(); - CMock_Verify(); - } - CMock_Destroy(); - UNITY_EXEC_TIME_STOP(); - UnityConcludeTest(); -} diff --git a/libraries/unity/auto/template_example.c b/libraries/unity/auto/template_example.c deleted file mode 100644 index 5a838b797..000000000 --- a/libraries/unity/auto/template_example.c +++ /dev/null @@ -1,84 +0,0 @@ -/* AUTOGENERATED FILE. DO NOT EDIT. */ - -/*=======Automagically Detected Files To Include=====*/ -#include "adc.h" -#include "gpio.h" -#include "interrupt.h" -#include "log.h" -#include "soft_timer.h" -#include "task_test_helpers.h" -#include "test_helpers.h" -#include "unity.h" - -/*=======External Functions This Runner Calls=====*/ -extern void setup_test(void); -extern void teardown_test(void); -extern void test_adc_pin_to_channel_conversion(); -extern void test_adc_read_temp(); -extern void test_pin_set_channel(void); -extern void test_pin_set_notification(void); -extern void test_pin_read_single(void); -extern void test_pin_read_continuous(void); -extern void test_adc_mock_reading(void); -extern void test_pin_single(void); -extern void test_pin_continuous(void); - -/*=======Mock Management=====*/ -static void CMock_Init(void) {} -static void CMock_Verify(void) {} -static void CMock_Destroy(void) {} - -/*=======Test Reset Options=====*/ -void resetTest(void); -void resetTest(void) { - teardown_test(); - CMock_Verify(); - CMock_Destroy(); - CMock_Init(); - setup_test(); -} -void verifyTest(void); -void verifyTest(void) { - CMock_Verify(); -} - -/*=======Test Runner Used To Run Each Test=====*/ -static void run_test(UnityTestFunction func, const char *name, UNITY_LINE_TYPE line_num) { - Unity.CurrentTestName = name; - Unity.CurrentTestLineNumber = line_num; - - Unity.NumberOfTests++; - UNITY_CLR_DETAILS(); - - vTaskSuspendAll(); - setup_test(); - xTaskResumeAll(); - - func(); - - UnityConcludeTest(); -} - -/*=======Parameterized Test Wrappers=====*/ -/*=======MAIN=====*/ -TASK(test_task, TASK_STACK_4096) { - UnityBegin("libraries/ms-common/test/test_adc.c"); - run_test(test_adc_pin_to_channel_conversion, "test_adc_pin_to_channel_conversion", 83); - run_test(test_adc_read_temp, "test_adc_read_temp", 111); - run_test(test_pin_set_channel, "test_pin_set_channel", 123); - run_test(test_pin_set_notification, "test_pin_set_notification", 135); - run_test(test_pin_read_single, "test_pin_read_single", 146); - run_test(test_pin_read_continuous, "test_pin_read_continuous", 157); - run_test(test_adc_mock_reading, "test_adc_mock_reading", 169); - run_test(test_pin_single, "test_pin_single", 183); - run_test(test_pin_continuous, "test_pin_continuous", 212); - - exit(UnityEnd()); -} - -int main(void) { - setup_test(); - tasks_init_task(test_task, configMAX_PRIORITIES - 1, NULL); - tasks_start(); - return 0; -} diff --git a/libraries/unity/auto/type_sanitizer.rb b/libraries/unity/auto/type_sanitizer.rb deleted file mode 100644 index d371ae9ba..000000000 --- a/libraries/unity/auto/type_sanitizer.rb +++ /dev/null @@ -1,6 +0,0 @@ -module TypeSanitizer - def self.sanitize_c_identifier(unsanitized) - # convert filename to valid C identifier by replacing invalid chars with '_' - unsanitized.gsub(/[-\/\\\.\,\s]/, '_') - end - end diff --git a/libraries/unity/unity_config.yml b/libraries/unity/unity_config.yml deleted file mode 100644 index ed9292d53..000000000 --- a/libraries/unity/unity_config.yml +++ /dev/null @@ -1,6 +0,0 @@ -:unity: - # Change setup/teardown names to conform to our coding style - :setup_name: setup_test - :teardown_name: teardown_test - # Parameterized tests are awesome! https://uwmidsun.atlassian.net/l/c/tXAB1D2b - :use_param_tests: true diff --git a/projects/leds/src/main.c b/projects/leds/src/main.c index d211a928b..a09993534 100644 --- a/projects/leds/src/main.c +++ b/projects/leds/src/main.c @@ -8,10 +8,10 @@ #include "tasks.h" static const GpioAddress leds[] = { - { .port = GPIO_PORT_B, .pin = 5 }, // - { .port = GPIO_PORT_B, .pin = 4 }, // - { .port = GPIO_PORT_B, .pin = 3 }, // - { .port = GPIO_PORT_A, .pin = 15 }, // + { .port = GPIO_PORT_B, .pin = 5 }, + { .port = GPIO_PORT_B, .pin = 4 }, + { .port = GPIO_PORT_B, .pin = 3 }, + { .port = GPIO_PORT_A, .pin = 15 }, }; void pre_loop_init() {} diff --git a/projects/leds/test/test_led_1.c b/projects/leds/test/test_led_1.c index 44170cb1d..0c16bb242 100644 --- a/projects/leds/test/test_led_1.c +++ b/projects/leds/test/test_led_1.c @@ -36,7 +36,11 @@ void test_leds(void) { LOG_DEBUG("blink\n"); for (uint8_t i = 0; i < SIZEOF_ARRAY(leds); i++) { gpio_toggle_state(&leds[i]); +#ifdef MS_PLATFORM_X86 + delay_ms(10); +#else delay_ms(200); +#endif } } } diff --git a/projects/leds/test/test_led_2.c b/projects/leds/test/test_led_2.c index 44170cb1d..0c16bb242 100644 --- a/projects/leds/test/test_led_2.c +++ b/projects/leds/test/test_led_2.c @@ -36,7 +36,11 @@ void test_leds(void) { LOG_DEBUG("blink\n"); for (uint8_t i = 0; i < SIZEOF_ARRAY(leds); i++) { gpio_toggle_state(&leds[i]); +#ifdef MS_PLATFORM_X86 + delay_ms(10); +#else delay_ms(200); +#endif } } } diff --git a/py/can/can.py b/py/can/can.py new file mode 100644 index 000000000..7ef6b9b24 --- /dev/null +++ b/py/can/can.py @@ -0,0 +1,65 @@ +import threading +import subprocess +import re +import time + +def set_output(output): + '''set the terminal output to the output string''' + # 'static' variable + if "output" not in set_output.__dict__: + set_output.output = "" + if output != set_output.output: + print("\033c", end="") + print(output) + set_output.output = output + + +CANDUMP_PATTERN = re.compile(r"(\w*) (\w*) \[([0-9])\] ((?:\w\w ?)+)") + + +def parse_line(line): + '''get the can id and data from a candump line''' + _, can_id, length, data = CANDUMP_PATTERN.match(line).groups() + can_id = int(can_id, base=16) + length = int(length) + data = bytes.fromhex(data) + + return can_id, data + + +class Can: + def __init__(self, device) -> None: + '''create a can controller, can be used to send and receive can messages to a can line''' + self.device = device + self.rx = {} + self.rx_thread = threading.Thread(target=self.rx_all) + self.rx_thread.start() + + def rx_all(self): + proc = subprocess.Popen(["candump", self.device]) + while True: + line = proc.stdout.readline().decode() + can_id, data = parse_line(line) + + # do things + + def tx(self, message): + subprocess.run(["cansend", self.device, message], check=False) + +# not sure how this will work out, needs reconciliation +def repeat(repeat_period, send_device_message, *args): + def threading_repeat(kill_process): + while not kill_process.is_set(): + send_device_message(*args) + time.sleep(repeat_period) + + kill_process = threading.Event() + + process_name = threading.Thread(target=threading_repeat, args=(kill_process,)) + process_name.start() + + return process_name, kill_process + +def end_repeat(send_device_message_process, kill_process): + kill_process.set() + send_device_message_process.join() diff --git a/py/can/config.json b/py/can/config.json new file mode 100644 index 000000000..3dd2ad825 --- /dev/null +++ b/py/can/config.json @@ -0,0 +1,4 @@ +{ + "no_lint": true, + "can": true +} diff --git a/requirements.txt b/requirements.txt index 9f82f3a96..9311d5b2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ python-can cantools Jinja2 PyYAML -pyserial \ No newline at end of file +pyserial +scons diff --git a/scons/build.scons b/scons/build.scons index 4eedf836d..622194251 100644 --- a/scons/build.scons +++ b/scons/build.scons @@ -21,10 +21,7 @@ SMOKE_DIR = ROOT.Dir('smoke') LIB_BIN_DIR = BIN_DIR.Dir('libraries') -CODEGEN_DIR = LIB_DIR.Dir("codegen") -BOARDS_DIR = CODEGEN_DIR.Dir("boards") -GENERATOR = CODEGEN_DIR.File("generator.py") -TEMPLATES_DIR = CODEGEN_DIR.Dir("templates") +CODEGEN_DIR = ROOT.Dir("autogen") LIBRARIES_INC_DIR = LIB_DIR.Dir("ms-common").Dir("inc") @@ -57,47 +54,44 @@ def get_lib_deps(entry): ########################################################### # Header file generation from jinja templates ########################################################### -autogen_sources = [BOARDS_DIR.glob("*"), TEMPLATES_DIR.glob("*"), GENERATOR] - +# print(CODEGEN_DIR.abspath) +autogen_sources = list(Path(CODEGEN_DIR.abspath).rglob("*")) +autogen_sources += list(Path(ROOT.abspath).glob("can/boards/*")) +# print([i.absolute() for i in autogen_sources]) +env.Command( + PY_DIR.File("can/message.py"), + autogen_sources, + f"python3 -m autogen python_can -o py/can" +) env.Command( LIBRARIES_INC_DIR.File("can_board_ids.h"), autogen_sources, - env.Action(f"python3 {GENERATOR} -f {LIBRARIES_INC_DIR} -t can_board_ids.h.jinja", - f"Autogen {LIBRARIES_INC_DIR}/can_board_ids.h") + f"python3 -m autogen can_board_ids -o libraries/ms-common/inc" ) def generate_can_files(env, project): project_can_dir = OBJ_DIR.Dir(project).Dir("can") - source_dir = project_can_dir.Dir("src") - header_dir = project_can_dir.Dir("inc") - header_files = [] - source_files = [] - source_command = f"-f {source_dir} -t" - header_command = f"-f {header_dir} -t" - - # TODO: Fix up system_can.dbc output directory - # project specific files - header_command += " can_codegen.h.jinja" - header_files.append(header_dir.File("can_codegen.h")) - for template in TEMPLATES_DIR.glob('_*.jinja'): - # name of output file - file_name = Path(project).stem + Path(template.name).stem - if ".c" in file_name: - source_command += " " + template.name - source_files.append(source_dir.File(file_name)) - else: - header_command += " " + template.name - header_files.append(header_dir.File(file_name)) - command = env.Action(f"python3 {GENERATOR} -b {Path(project).stem} {source_command} {header_command}", - f"Autogen {project} can files") - # print([f.path for f in header_files + source_files]) - env.Command(header_files + source_files, autogen_sources, command) + output_files = [] + + project_name = Path(project).stem + can_template_dir = Path(str(CODEGEN_DIR), "templates/can") + for template in can_template_dir.glob('*/*.jinja'): + template_path = template.relative_to(can_template_dir) + output_name = str(template_path) \ + .replace(r"{{board}}", project_name) \ + .replace(".jinja", "") + + output_files.append(project_can_dir.File(output_name)) + + # print([f.path for f in output_files]) + env.Command(output_files, autogen_sources, + f"python3 -m autogen can -o {project_can_dir.path}") # Add a VariantDir that point to can folder. Create the can target specific for the project VariantDir(project_can_dir, ROOT.Dir('can'), duplicate=0) - return src(project_can_dir), [header_dir] + inc(ROOT.Dir("can")) + return src(project_can_dir), [project_can_dir.Dir("inc")] + inc(ROOT.Dir("can")) ########################################################### @@ -167,6 +161,10 @@ for entry in PROJ_DIR.glob('*') + SMOKE_DIR.glob('*'): for entry in PY_DIR.glob("*", exclude=["*.*", "__pycache__"]): target = env.Command(entry.path, [], f"PYTHONPATH={PY_DIR.path} python3 {entry.path}/main.py") + config = parse_config(entry) + if config["can"]: + # Depends(target, PY_DIR.File("can/message.py")) + pass Alias(entry.path, target) Alias(entry.name, target) diff --git a/scons/common.py b/scons/common.py index f429f0f20..3d0f76452 100644 --- a/scons/common.py +++ b/scons/common.py @@ -33,6 +33,7 @@ def flash_run(entry): except: print() print("Flashing requires a controller board to be connected, use --platform=x86 for x86 targets") + exit(1) OPENOCD = 'openocd' OPENOCD_SCRIPT_DIR = '/usr/share/openocd/scripts/' diff --git a/scons/mem_report.py b/scons/mem_report.py index a216b1775..1b5d893e2 100644 --- a/scons/mem_report.py +++ b/scons/mem_report.py @@ -6,8 +6,8 @@ # Path to main project binary file path_to_binary = sys.argv[1] -########################################################## -# Size Info For Main Binary +########################################################## +# Size Info For Main Binary ########################################################## print('Size Info For Main Binary' + '(' + path_to_binary + '):\n') @@ -16,70 +16,75 @@ size_info = stream.read().rstrip() print(size_info) -########################################################## -# Size Info For Libraries With Top 5 Size Usage +########################################################## +# Size Info For Libraries With Top 5 Size Usage ########################################################## if (os.path.isdir('build/arm/bin/libraries')): - stream = os.popen('size build/arm/bin/libraries/*') - size_info = stream.readlines() - - # remove the first element (columns) - columns = size_info[0] - size_info.pop(0) - - size_info_list = [] - for size in size_info: - size_info_list.append(size.split(None, 5)) - - # sorts by the 'dec' and 'bss' columns - size_info_dec = sorted(size_info_list, key = lambda x: int(x[3]), reverse=True) - size_info_bss = sorted(size_info_list, key = lambda x: int(x[2]), reverse=True) - - print("\n\nSize Info for Top 5 Libraries with the Greatest Total Size('dec'):\n") - for i in range (0,5): - size_info_dec[i][5] = size_info_dec[i][5].strip() - print("\t".join(size_info_dec[i])) - - print("\n\nSize Info for Libraries with the Greatest Block Starting Symbol('bss'):\n") - for i in range (0,5): - size_info_bss[i][5] = size_info_bss[i][5].strip() - print("\t".join(size_info_bss[i])) + stream = os.popen('size build/arm/bin/libraries/*') + size_info = stream.readlines() + + # remove the first element (columns) + columns = size_info[0] + size_info.pop(0) + + size_info_list = [] + for size in size_info: + size_info_list.append(size.split(None, 5)) + + # sorts by the 'dec' and 'bss' columns + size_info_dec = sorted( + size_info_list, key=lambda x: int(x[3]), reverse=True) + size_info_bss = sorted( + size_info_list, key=lambda x: int(x[2]), reverse=True) + + print("\n\nSize Info for Top 5 Libraries with the Greatest Total Size('dec'):\n") + for i in range(0, 5): + size_info_dec[i][5] = size_info_dec[i][5].strip() + print("\t".join(size_info_dec[i])) + + print("\n\nSize Info for Libraries with the Greatest Block Starting Symbol('bss'):\n") + for i in range(0, 5): + size_info_bss[i][5] = size_info_bss[i][5].strip() + print("\t".join(size_info_bss[i])) else: - print("\n\nNo libaries info found") - + print("\n\nNo libaries info found") + ########################################################## -# Display all bss objects with size >= 512 +# Display all bss objects with size >= 512 ########################################################## + def split_str(line): - return line.split() + return line.split() + bss_data = [] with open('build/out.map') as file: - rows = map(split_str, file) - for row in rows: - if (len(row) != 0 and row[0].startswith(".bss.")): - if (len(row) != 1): - bss_data.append(row) - else: - row.extend(next(rows)) - bss_data.append(row) - + rows = map(split_str, file) + for row in rows: + if (len(row) != 0 and row[0].startswith(".bss.")): + if (len(row) != 1): + bss_data.append(row) + else: + row.extend(next(rows)) + bss_data.append(row) + if (len(bss_data) != 0): - # sorts bss_data by size of the object - def order_by_size(data): - return int(data[2], 16) - - sorted_bss_data = sorted(bss_data, key=order_by_size, reverse=True) - - print('\n\nBSS Object info with Greatest Total Size (>= 512 bytes):\n') - for row in sorted_bss_data: - size = row[2] = int(row[2], 16) - if (size >= 512): - print(row[0] + '\n') - print('\t\t\t\t' + row[1] + ' ' + str(row[2]) + ' ' + row[3] + '\n') - + # sorts bss_data by size of the object + def order_by_size(data): + return int(data[2], 16) + + sorted_bss_data = sorted(bss_data, key=order_by_size, reverse=True) + + print('\n\nBSS Object info with Greatest Total Size (>= 512 bytes):\n') + for row in sorted_bss_data: + size = row[2] = int(row[2], 16) + if (size >= 512): + print(row[0] + '\n') + print('\t\t\t\t' + row[1] + ' ' + + str(row[2]) + ' ' + row[3] + '\n') + else: - print("\nNo BSS data found\n") + print("\nNo BSS data found\n") diff --git a/scons/new_target.scons b/scons/new_target.scons index 368aca487..84f86c124 100644 --- a/scons/new_target.scons +++ b/scons/new_target.scons @@ -1,7 +1,3 @@ -import json -import jinja2 -from pathlib import Path - ########################################################### # Variables setup ########################################################### @@ -10,84 +6,15 @@ Import('VARS') TARGET = VARS.get("TARGET") -# default configuration for projects -DEFAULT_CONFIG = {"libs": ["FreeRTOS", "ms-common"]} - - -def new_task(target, task_name): - '''Create a new task within target''' - DATA = {"task_name": task_name} - target_path = Path(target) - target_path.joinpath("inc").mkdir() - target_path.joinpath("src").mkdir() - - templateLoader = jinja2.FileSystemLoader(searchpath="scons/template") - env = jinja2.Environment(loader=templateLoader) - - template = env.get_template("_task.h.jinja") - output = template.render(data=DATA) - target_path.joinpath(f'inc/{task_name}_task.h').write_text(output) - - template = env.get_template("_task.c.jinja") - output = template.render(data=DATA) - target_path.joinpath(f'src/{task_name}_task.c').write_text(output) - - -def new_target(target, source, env): - """Creates a new project or library. - - Creates a subfolder in the appropriate folder with the specified name - with the following structure: - - project/library/smoke - - name - - inc - - README.md - - config.json - - src - - test - py - - name - - __init__.py - - main.py - """ - if not TARGET: - print("Missing target. Expected --project=..., or --library=..., or --python=..., or --smoke=...") - return - - task_name = GetOption("task") - if task_name: - new_task(TARGET, task_name) - return - - target_dir = Path(TARGET) - target_dir.mkdir(parents=True) - - if TARGET.startswith('py'): - target_dir.joinpath('__init__.py').touch() - target_dir.joinpath('main.py').touch() - print(f'Created new {TARGET}') - return - - for folder in ['src', 'inc', 'test']: - target_dir.joinpath(folder).mkdir(parents=True, exist_ok=True) - - config = [] - if TARGET.startswith("project") or TARGET.startswith("smoke"): - config = DEFAULT_CONFIG - - target_dir.joinpath("config.json").write_text(json.dumps(config, indent=4)) - - readme_template = Path("scons/template/README.md") - readme_file = target_dir.joinpath("README.md") - readme_file.write_text(readme_template.read_text() + target_dir.name) - - main_c_template = Path("scons/template/main.c") - main_c_file = target_dir.joinpath("src/main.c") - main_c_file.write_text(main_c_template.read_text()) - - print(f'Created new {TARGET}') +if not TARGET: + print("Missing target. Expected --project=..., or --library=..., or --python=..., or --smoke=...") + raise Exception +task_name = GetOption("task") +if task_name: + command = f"python3 -m autogen new_task -o {TARGET}/{task_name}" +else: + command = f"python3 -m autogen new -o {TARGET}" -new = Command('new_proj.txt', [], new_target) +new = Command('new_proj.txt', [], command) Alias('new', new) diff --git a/scons/template/_task.c.jinja b/scons/template/_task.c.jinja deleted file mode 100644 index 2e8050e36..000000000 --- a/scons/template/_task.c.jinja +++ /dev/null @@ -1,41 +0,0 @@ -#include "log.h" -#include "tasks.h" -#include "{{data.task_name}}_task.h" - -static SemaphoreHandle_t s_{{data.task_name}}_sem_handle; -static StaticSemaphore_t s_{{data.task_name}}_sem; - -void run_{{data.task_name}}_cycle() { - BaseType_t ret = xSemaphoreGive(s_{{data.task_name}}_sem_handle); - - if (ret == pdFALSE) { - return STATUS_CODE_INTERNAL_ERROR; - } - - return STATUS_CODE_OK; -} - -TASK({{data.task_name}}, TASK_MIN_STACK_SIZE) { - int counter = 0; - while (true) { - xSemaphoreTake(s_{{data.task_name}}_sem_handle, portMAX_DELAY); - counter++; - run_{{data.task_name}}_fast_cycle(); - if ((counter % 10) == 0) - run_{{data.task_name}}_medium_cycle(); - if ((counter % 100) == 0) - run_{{data.task_name}}_slow_cycle(); - xSemaphoreGive(s_end_task_sem); - } -} - -void run_{{data.task_name}}_fast_cycle() {} - -void run_{{data.task_name}}_medium_cycle() {} - -void run_{{data.task_name}}_slow_cycle() {} - -StatusCode init_{{data.task_name}}() { - status_ok_or_return(tasks_init_task({{data.task_name}}, TASK_PRIORITY(2), NULL)); - return STATUS_CODE_OK; -} \ No newline at end of file diff --git a/scons/template/_task.h.jinja b/scons/template/_task.h.jinja deleted file mode 100644 index 238f4cd17..000000000 --- a/scons/template/_task.h.jinja +++ /dev/null @@ -1,14 +0,0 @@ -#include "status.h" - -/* - * Name: {{data.task_name}} - * Description: What this task does and to use it - * Author: - * Date: -*/ - -void run_{{data.task_name}}_cycle(); -void run_{{data.task_name}}_fast_cycle(); -void run_{{data.task_name}}_medium_cycle(); -void run_{{data.task_name}}_slow_cycle(); -StatusCode init_{{data.task_name}}(); \ No newline at end of file diff --git a/scons/test.scons b/scons/test.scons index 317d95ff1..2e0c83a99 100644 --- a/scons/test.scons +++ b/scons/test.scons @@ -19,13 +19,11 @@ PROJ_DIR = ROOT.Dir('projects') LIB_DIR = ROOT.Dir('libraries') SMOKE_DIR = ROOT.Dir('smoke') -GEN_RUNNER = 'libraries/unity/auto/generate_test_runner.py' -GEN_RUNNER_CONFIG = 'libraries/unity/unity_config.yml' - ########################################################### # Create appropriate test targets, (when lib/proj target is defined) ########################################################### run_list = [] +autogen_sources = list(ROOT.glob("autogen/*")) def add_to_run_list(target, source, env): @@ -50,17 +48,14 @@ def add_test_targets(target, source, env): mocks = config.get("mocks", {}).get(test_module_name, []) mock_link_flags = [f'-Wl,-wrap,{mock}' for mock in mocks] - runner_exec = TEST_DIR.File(f"{entry.path}/bin/{test_module_name}") - runner_file = TEST_DIR.File(test_file.path) - autogen = env.Action(f'python3 {GEN_RUNNER} {GEN_RUNNER_CONFIG} {test_file.path} {runner_file.path}', - f'Autogen {test_file.path} test_runner') runner_file = env.Command( runner_file, - [test_file, ROOT.Dir("libraries/unity/auto").glob("*")], - autogen) - test_sources = [runner_file, OBJ_DIR.File(test_file.path)] + [test_file, autogen_sources], + f'python3 -m autogen test -o {runner_file.path}') + runner_exec = TEST_DIR.File(f"{entry.path}/bin/{test_module_name}") + test_sources = [runner_file, OBJ_DIR.File(test_file.path)] test_target = env.Program( target=runner_exec, source=sources_no_main + test_sources, @@ -83,6 +78,11 @@ def run_test(target, source, env): fails = [] for test_name, test_file in zip(source, run_list): if PLATFORM == "x86": + # set up vcan device if not setup + if subprocess.run("ip link show vcan0".split(" "), stdout=subprocess.DEVNULL).returncode: + subprocess.run("sudo ip link add dev vcan0 type vcan".split(" ")) + subprocess.run("sudo ip link set up vcan0".split(" ")) + # run test file test_process = subprocess.run(test_file.path) if test_process.returncode != 0: fails.append(test_name)