From fcd2e82114d8bb80d019ba5bc42b6a933f0cdb49 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 22 Nov 2025 19:47:55 +0100 Subject: [PATCH 01/11] Update device tree overlay and driver enable functions for stepper control --- app/boards/esp32s3_devkitc_procpu.overlay | 19 +++++++++++++------ app/src/ZephyrStepper.cpp | 2 +- west.yml | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/boards/esp32s3_devkitc_procpu.overlay b/app/boards/esp32s3_devkitc_procpu.overlay index a352ea4..f4d36e1 100644 --- a/app/boards/esp32s3_devkitc_procpu.overlay +++ b/app/boards/esp32s3_devkitc_procpu.overlay @@ -4,24 +4,31 @@ */ /* Devicetree overlay enabling the Moonlite focuser firmware on ESP32-S3 DevKitC - * (PROCPU). The TMC2209 stepper driver is exposed via the "stepper" and - * "stepper-drv" aliases, while the zephyr,user node supplies the UART phandle + * (PROCPU). The zephyr,gpio-step-dir-controller drives the step/dir pins + * referenced by the "stepper" alias, while the dedicated TMC2209 device is + * referenced by the "stepper-drv" alias so the application can toggle the + * external driver rails. The zephyr,user node supplies the UART phandle * consumed by the application. */ / { aliases { stepper = &focuser_stepper; - stepper-drv = &focuser_stepper; + stepper-drv = &focuser_stepper_drv; }; - focuser_stepper: focuser_stepper { + focuser_stepper_drv: focuser_stepper_drv { compatible = "adi,tmc2209"; + en-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>; + micro-step-res = <1>; + status = "okay"; + }; + + focuser_stepper: focuser_stepper { + compatible = "zephyr,gpio-step-dir-controller"; step-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>; dir-gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>; - en-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>; step-width-ns = <10000>; - micro-step-res = <1>; status = "okay"; }; diff --git a/app/src/ZephyrStepper.cpp b/app/src/ZephyrStepper.cpp index 53e37bc..a4e6073 100644 --- a/app/src/ZephyrStepper.cpp +++ b/app/src/ZephyrStepper.cpp @@ -93,5 +93,5 @@ int ZephyrFocuserStepper::enable_driver(bool enable) return 0; } - return enable ? stepper_enable(m_stepper_drv) : stepper_disable(m_stepper_drv); + return enable ? stepper_drv_enable(m_stepper_drv) : stepper_drv_disable(m_stepper_drv); } diff --git a/west.yml b/west.yml index 11f6ef6..ef6a4b5 100644 --- a/west.yml +++ b/west.yml @@ -15,7 +15,7 @@ manifest: projects: - name: zephyr - remote: andre-stefanov + remote: jilaypandya revision: feature/introduce-step-dir-functions import: # By using name-allowlist we can clone only the modules that are From 4d26f3ecbf966d11fd946f09db3e51ed8b708955 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 22 Nov 2025 19:49:16 +0100 Subject: [PATCH 02/11] Update workflow triggers to only run on main branch for push events --- .github/workflows/docs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c261fcc..28fe636 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,11 @@ name: Documentation -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: permissions: contents: read From 109361991a21318794037852af32ed15a628b6dd Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 22 Nov 2025 22:22:34 +0100 Subject: [PATCH 03/11] Update device tree overlay for stepper control and adjust project configurations --- app/boards/qemu_cortex_m0.overlay | 20 ++++++++++---------- app/prj.conf | 2 ++ app/sample.yaml | 1 - west.yml | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/boards/qemu_cortex_m0.overlay b/app/boards/qemu_cortex_m0.overlay index 9713d93..5003194 100644 --- a/app/boards/qemu_cortex_m0.overlay +++ b/app/boards/qemu_cortex_m0.overlay @@ -3,24 +3,24 @@ * * Devicetree overlay enabling the Moonlite focuser firmware on the * qemu_cortex_m0 board. The design uses the generic GPIO H-bridge stepper - * controller so the application can run entirely in the simulator. + * controller for motion ("stepper" alias) plus the Zephyr fake stepper + * driver ("stepper-drv" alias) so the application can exercise the driver + * enable path under simulation. */ / { aliases { stepper = &focuser_stepper; - stepper-drv = &focuser_stepper; + stepper-drv = &focuser_stepper_drv; + }; + + focuser_stepper_drv: focuser_stepper_drv { + compatible = "zephyr,fake-stepper-driver"; + status = "okay"; }; focuser_stepper: focuser_stepper { - compatible = "zephyr,h-bridge-stepper"; - /* Four phase outputs feeding a simulated H-bridge. */ - gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>, - <&gpio0 2 GPIO_ACTIVE_HIGH>, - <&gpio0 3 GPIO_ACTIVE_HIGH>, - <&gpio0 4 GPIO_ACTIVE_HIGH>; - en-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; - micro-step-res = <1>; + compatible = "zephyr,fake-stepper-controller"; status = "okay"; }; diff --git a/app/prj.conf b/app/prj.conf index 6e811d1..bdfe2e6 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -24,6 +24,8 @@ CONFIG_LOG_BACKEND_UART=y # Set the application log level. CONFIG_APP_LOG_LEVEL_INF=y +CONFIG_COUNTER=y + # Enable Zephyr's generic stepper controller subsystem and the TMC2209 backend. CONFIG_STEPPER=y CONFIG_STEPPER_ADI_TMC2209=y diff --git a/app/sample.yaml b/app/sample.yaml index 7cdeeac..4651ace 100644 --- a/app/sample.yaml +++ b/app/sample.yaml @@ -8,7 +8,6 @@ common: build_only: true integration_platforms: - esp32s3_devkitc/esp32s3/procpu - - qemu_cortex_m0 tests: app.default: {} app.debug: diff --git a/west.yml b/west.yml index ef6a4b5..11f6ef6 100644 --- a/west.yml +++ b/west.yml @@ -15,7 +15,7 @@ manifest: projects: - name: zephyr - remote: jilaypandya + remote: andre-stefanov revision: feature/introduce-step-dir-functions import: # By using name-allowlist we can clone only the modules that are From 1c47763fe82c0dfb5a82ef6b06e331c808345f65 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Mon, 24 Nov 2025 20:53:55 +0100 Subject: [PATCH 04/11] Update device tree overlays and configurations for stepper control and UART handling --- app/boards/esp32s3_devkitc_procpu.overlay | 29 ++- app/prj.conf | 12 +- app/src/Configuration.hpp | 69 ++---- app/src/main.cpp | 19 +- tests/app/focuser/CMakeLists.txt | 18 ++ .../app/focuser}/boards/qemu_cortex_m0.conf | 2 - .../focuser}/boards/qemu_cortex_m0.overlay | 6 - tests/app/focuser/prj.conf | 14 ++ tests/app/focuser/src/main.cpp | 202 ++++++++++++++++++ tests/app/focuser/testcase.yaml | 7 + 10 files changed, 296 insertions(+), 82 deletions(-) create mode 100644 tests/app/focuser/CMakeLists.txt rename {app => tests/app/focuser}/boards/qemu_cortex_m0.conf (87%) rename {app => tests/app/focuser}/boards/qemu_cortex_m0.overlay (57%) create mode 100644 tests/app/focuser/prj.conf create mode 100644 tests/app/focuser/src/main.cpp create mode 100644 tests/app/focuser/testcase.yaml diff --git a/app/boards/esp32s3_devkitc_procpu.overlay b/app/boards/esp32s3_devkitc_procpu.overlay index f4d36e1..e0364df 100644 --- a/app/boards/esp32s3_devkitc_procpu.overlay +++ b/app/boards/esp32s3_devkitc_procpu.overlay @@ -12,9 +12,17 @@ */ / { - aliases { - stepper = &focuser_stepper; - stepper-drv = &focuser_stepper_drv; + chosen { + zephyr,console = &uart1; + zephyr,log-uart = &focuser_log_uarts; + focuser,uart = &uart0; + focuser,stepper = &focuser_stepper; + focuser,stepper-drv = &focuser_stepper_drv; + }; + + focuser_log_uarts: focuser_log_uarts { + compatible = "zephyr,log-uart"; + uarts = <&uart1>; }; focuser_stepper_drv: focuser_stepper_drv { @@ -31,16 +39,20 @@ step-width-ns = <10000>; status = "okay"; }; - - zephyr,user { - uart_handler = <&uart1>; - }; }; +/** +* Moonlite focuser console UART configuration +*/ &uart0 { current-speed = <9600>; + status = "okay"; }; +/** +* Moonlite focuser control UART configuration. +* Used for +*/ &uart1 { status = "okay"; current-speed = <9600>; @@ -48,6 +60,9 @@ pinctrl-names = "default"; }; +/** +* Pin control settings +*/ &pinctrl { moonlite_uart1_default: moonlite_uart1_default { group1 { diff --git a/app/prj.conf b/app/prj.conf index bdfe2e6..3f88581 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -10,16 +10,16 @@ CONFIG_MOONLITE=y # Provide heap storage for std::string and other dynamic allocations. CONFIG_HEAP_MEM_POOL_SIZE=4096 -# Route logs and console I/O over UART for the focuser serial protocol. +# Enable Serial/UART driver CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# Enable Console (printk) CONFIG_CONSOLE=y -CONFIG_STDOUT_CONSOLE=y -CONFIG_PRINTK=y CONFIG_UART_CONSOLE=y -CONFIG_UART_INTERRUPT_DRIVEN=y + +# Enable Logging (LOG_INF, LOG_ERR) CONFIG_LOG=y -CONFIG_LOG_MODE_IMMEDIATE=y -CONFIG_LOG_BACKEND_UART=y # Set the application log level. CONFIG_APP_LOG_LEVEL_INF=y diff --git a/app/src/Configuration.hpp b/app/src/Configuration.hpp index c505268..ed35002 100644 --- a/app/src/Configuration.hpp +++ b/app/src/Configuration.hpp @@ -2,63 +2,38 @@ #include #include +#include #include -#define FOCUSER_NODE DT_PATH(zephyr_user) -#define STEPPER_ALIAS DT_ALIAS(stepper) -#define STEPPER_DRV_ALIAS DT_ALIAS(stepper_drv) - -#if !DT_NODE_EXISTS(FOCUSER_NODE) -#error "zephyr,user node must provide focuser pin assignments" -#endif - -#if !DT_NODE_HAS_PROP(FOCUSER_NODE, uart_handler) -#error "zephyr,user node requires uart_handler phandle" -#endif +namespace config +{ -#if !DT_NODE_HAS_STATUS(STEPPER_ALIAS, okay) -#error "stepper alias must reference an enabled stepper controller" +#if !DT_HAS_CHOSEN(focuser_uart) +#error "UART device is required for Moonlite serial protocol" +#else + constexpr auto uart = DEVICE_DT_GET(DT_CHOSEN(focuser_uart)); #endif -#if !DT_NODE_HAS_STATUS(STEPPER_DRV_ALIAS, okay) -#error "stepper-drv alias must reference an enabled stepper driver" +#if !DT_HAS_CHOSEN(focuser_stepper) +#error "Stepper device is required for Moonlite serial protocol" +#else + constexpr auto stepper = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper)); #endif -#if !DT_HAS_CHOSEN(zephyr_console) -#error "Console device is required for Moonlite serial protocol" +#if !DT_HAS_CHOSEN(focuser_stepper_drv) +#error "Stepper driver device is required for Moonlite serial protocol" +#else + constexpr auto stepper_drv = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper_drv)); #endif -namespace config -{ - -struct ThreadConfig -{ - std::size_t stack_size; - int priority; -}; - -inline constexpr ThreadConfig kFocuserThread{CONFIG_FOCUSER_THREAD_STACK_SIZE, K_PRIO_PREEMPT(4)}; -inline constexpr ThreadConfig kSerialThread{CONFIG_SERIAL_THREAD_STACK_SIZE, K_PRIO_PREEMPT(5)}; + struct ThreadConfig + { + std::size_t stack_size; + int priority; + }; -inline const struct device *console_device() -{ - return DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); -} - -inline const struct device *uart_handler_device() -{ - return DEVICE_DT_GET(DT_PHANDLE(FOCUSER_NODE, uart_handler)); -} - -inline const struct device *stepper_device() -{ - return DEVICE_DT_GET(STEPPER_ALIAS); -} - -inline const struct device *stepper_driver_device() -{ - return DEVICE_DT_GET(STEPPER_DRV_ALIAS); -} + inline constexpr ThreadConfig kFocuserThread{CONFIG_FOCUSER_THREAD_STACK_SIZE, K_PRIO_PREEMPT(4)}; + inline constexpr ThreadConfig kSerialThread{CONFIG_SERIAL_THREAD_STACK_SIZE, K_PRIO_PREEMPT(5)}; } // namespace config \ No newline at end of file diff --git a/app/src/main.cpp b/app/src/main.cpp index 9a56835..aa90ae7 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -23,15 +23,10 @@ LOG_MODULE_REGISTER(focuser, CONFIG_APP_LOG_LEVEL); namespace { - const struct device *const g_console = config::console_device(); - const struct device *const g_uart_handler_dev = config::uart_handler_device(); - const struct device *const g_stepper = config::stepper_device(); - const struct device *const g_stepper_drv = config::stepper_driver_device(); - - ZephyrFocuserStepper g_stepper_adapter(g_stepper, g_stepper_drv); + ZephyrFocuserStepper g_stepper_adapter(config::stepper, config::stepper_drv); Focuser g_focuser(g_stepper_adapter); FocuserThread g_focuser_thread(g_focuser); - UartHandler g_uart_handler(g_uart_handler_dev); + UartHandler g_uart_handler(config::uart); UartThread g_uart_thread(g_focuser, g_uart_handler); } // namespace @@ -40,13 +35,7 @@ int main(void) { LOG_INF("Moonlite focuser firmware %s", APP_VERSION_STRING); - if (!device_is_ready(g_console)) - { - LOG_ERR("Console device not ready"); - return -ENODEV; - } - - if (!device_is_ready(g_uart_handler_dev)) + if (!device_is_ready(config::uart)) { LOG_ERR("UART handler device not ready"); return -ENODEV; @@ -55,12 +44,14 @@ int main(void) int ret = g_uart_handler.init(); if (ret != 0) { + LOG_ERR("Failed to initialize UART handler"); return ret; } ret = g_focuser.initialise(); if (ret != 0) { + LOG_ERR("Failed to initialize focuser (%d)", ret); return ret; } diff --git a/tests/app/focuser/CMakeLists.txt b/tests/app/focuser/CMakeLists.txt new file mode 100644 index 0000000..f0177e2 --- /dev/null +++ b/tests/app/focuser/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(focuser_app_test) + +set(APP_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../..) + +target_sources(app PRIVATE + src/main.cpp + ${APP_ROOT}/app/src/Focuser.cpp + ${APP_ROOT}/app/src/ZephyrStepper.cpp +) + +target_include_directories(app PRIVATE + ${APP_ROOT}/app/src + ${CMAKE_CURRENT_LIST_DIR}/include +) + +zephyr_compile_definitions(FFF_ARG_HISTORY_LEN=4) diff --git a/app/boards/qemu_cortex_m0.conf b/tests/app/focuser/boards/qemu_cortex_m0.conf similarity index 87% rename from app/boards/qemu_cortex_m0.conf rename to tests/app/focuser/boards/qemu_cortex_m0.conf index 6f3ce99..b3a011c 100644 --- a/app/boards/qemu_cortex_m0.conf +++ b/tests/app/focuser/boards/qemu_cortex_m0.conf @@ -4,8 +4,6 @@ # simulator lacks a TMC2209 so we swap in Zephyr's GPIO H-bridge stepper # driver which only depends on the virtual GPIO controller. -CONFIG_STEPPER_ADI_TMC2209=n -CONFIG_H_BRIDGE_STEPPER=y CONFIG_GPIO=y CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_MAIN_STACK_SIZE=2048 diff --git a/app/boards/qemu_cortex_m0.overlay b/tests/app/focuser/boards/qemu_cortex_m0.overlay similarity index 57% rename from app/boards/qemu_cortex_m0.overlay rename to tests/app/focuser/boards/qemu_cortex_m0.overlay index 5003194..965f59f 100644 --- a/app/boards/qemu_cortex_m0.overlay +++ b/tests/app/focuser/boards/qemu_cortex_m0.overlay @@ -1,11 +1,5 @@ /* * SPDX-License-Identifier: Apache-2.0 - * - * Devicetree overlay enabling the Moonlite focuser firmware on the - * qemu_cortex_m0 board. The design uses the generic GPIO H-bridge stepper - * controller for motion ("stepper" alias) plus the Zephyr fake stepper - * driver ("stepper-drv" alias) so the application can exercise the driver - * enable path under simulation. */ / { diff --git a/tests/app/focuser/prj.conf b/tests/app/focuser/prj.conf new file mode 100644 index 0000000..2d06dd2 --- /dev/null +++ b/tests/app/focuser/prj.conf @@ -0,0 +1,14 @@ +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=2048 +CONFIG_ZTEST_ASSERT_VERBOSE=1 +CONFIG_CPP=y +CONFIG_STD_CPP20=y +CONFIG_REQUIRES_FULL_LIBCPP=y +CONFIG_NEWLIB_LIBC=y +CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=0 +CONFIG_HEAP_MEM_POOL_SIZE=1024 +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_PRINTK=y +CONFIG_STEPPER=y +CONFIG_FAKE_STEPPER=y diff --git a/tests/app/focuser/src/main.cpp b/tests/app/focuser/src/main.cpp new file mode 100644 index 0000000..f26b81c --- /dev/null +++ b/tests/app/focuser/src/main.cpp @@ -0,0 +1,202 @@ +#include +#include + +#include + +#include + +#include +#include +#include + +#include "Focuser.hpp" +#include "ZephyrStepper.hpp" + +#ifndef CONFIG_APP_LOG_LEVEL +#define CONFIG_APP_LOG_LEVEL LOG_LEVEL_INF +#endif + +LOG_MODULE_REGISTER(focuser, CONFIG_APP_LOG_LEVEL); + +DEFINE_FFF_GLOBALS; + +namespace +{ + +const struct device *const k_stepper_controller = DEVICE_DT_GET(DT_ALIAS(stepper)); +const struct device *const k_stepper_driver = DEVICE_DT_GET(DT_ALIAS(stepper_drv)); + +void assert_stepper_devices_ready() +{ + zassert_true(device_is_ready(k_stepper_controller), "Fake stepper controller not ready"); + zassert_true(device_is_ready(k_stepper_driver), "Fake stepper driver not ready"); +} + +class ReadyOverrideStepper : public FocuserStepper +{ +public: + ReadyOverrideStepper(FocuserStepper &impl, bool ready_override) + : m_impl(impl), m_ready_override(ready_override) + { + } + + bool is_ready() const override + { + return m_ready_override && m_impl.is_ready(); + } + + int set_reference_position(int32_t position) override + { + return m_impl.set_reference_position(position); + } + + int set_microstep_interval(uint64_t interval_ns) override + { + return m_impl.set_microstep_interval(interval_ns); + } + + int move_to(int32_t target) override + { + return m_impl.move_to(target); + } + + int is_moving(bool &moving) override + { + return m_impl.is_moving(moving); + } + + int stop() override + { + return m_impl.stop(); + } + + int get_actual_position(int32_t &position) override + { + return m_impl.get_actual_position(position); + } + + int enable_driver(bool enable) override + { + return m_impl.enable_driver(enable); + } + +private: + FocuserStepper &m_impl; + bool m_ready_override; +}; + +} // namespace + +ZTEST(focuser_app, test_initialise_requires_ready_stepper) +{ + assert_stepper_devices_ready(); + ZephyrFocuserStepper hw_stepper(k_stepper_controller, k_stepper_driver); + ReadyOverrideStepper stepper(hw_stepper, false); + Focuser focuser(stepper); + + const int ret = focuser.initialise(); + + zassert_equal(ret, -ENODEV, "initialise should fail when hardware is not ready"); + zassert_equal(fake_stepper_set_reference_position_fake.call_count, 0U, + "should not set reference position on failure"); + zassert_equal(fake_stepper_set_microstep_interval_fake.call_count, 0U, + "should not update step interval on failure"); + zassert_equal(fake_stepper_drv_enable_fake.call_count, 1U, + "only constructor enable should run"); +} + +ZTEST(focuser_app, test_initialise_configures_stepper_when_ready) +{ + assert_stepper_devices_ready(); + ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); + Focuser focuser(stepper); + + const int ret = focuser.initialise(); + + zassert_equal(ret, 0, "initialise should succeed when hardware is ready"); + zassert_equal(fake_stepper_set_reference_position_fake.call_count, 1U, + "reference position should be set once"); + zassert_equal(fake_stepper_set_reference_position_fake.arg1_val, 0, + "reference position should reset to zero"); + zassert_equal(fake_stepper_set_microstep_interval_fake.call_count, 1U, + "step interval should be applied once"); + zassert_equal(fake_stepper_set_microstep_interval_fake.arg1_val, 500000ULL, + "default speed uses 500us interval"); + zassert_equal(fake_stepper_drv_enable_fake.call_count, 2U, + "constructor plus initialise enable calls expected"); + zassert_equal(fake_stepper_drv_disable_fake.call_count, 0U, + "driver should remain enabled after init"); +} + +ZTEST(focuser_app, test_set_speed_clamps_to_minimum_and_updates_interval) +{ + assert_stepper_devices_ready(); + ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); + Focuser focuser(stepper); + zassert_ok(focuser.initialise(), "initialise precondition"); + + const unsigned int initial_microstep_calls = + fake_stepper_set_microstep_interval_fake.call_count; + + focuser.setSpeed(0); + zassert_equal(fake_stepper_set_microstep_interval_fake.call_count, + initial_microstep_calls + 1U, "setSpeed should update timing once"); + zassert_equal(fake_stepper_set_microstep_interval_fake.arg1_val, 500000ULL, + "speed 0 should clamp to 1x interval"); + zassert_equal(focuser.getSpeed(), 1, "speed should clamp to 1"); + + focuser.setSpeed(40); + zassert_equal(fake_stepper_set_microstep_interval_fake.call_count, + initial_microstep_calls + 2U, "second setSpeed call should reapply interval"); + zassert_equal(fake_stepper_set_microstep_interval_fake.arg1_val, 10000000ULL, + "high multiplier should clamp to 100 sps"); + zassert_equal(focuser.getSpeed(), 40, "speed multiplier should store requested value"); +} + +ZTEST(focuser_app, test_stop_stops_motion_and_disables_driver) +{ + assert_stepper_devices_ready(); + ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); + Focuser focuser(stepper); + zassert_ok(focuser.initialise(), "initialise precondition"); + + const unsigned int initial_stop_calls = fake_stepper_stop_fake.call_count; + const unsigned int initial_get_pos_calls = fake_stepper_get_actual_position_fake.call_count; + const unsigned int initial_disable_calls = fake_stepper_drv_disable_fake.call_count; + + fake_stepper_get_actual_position_fake.custom_fake = + [](const struct device *, int32_t *position) { + if (position != nullptr) + { + *position = 0x4321; + } + return 0; + }; + + focuser.stop(); + + zassert_equal(fake_stepper_stop_fake.call_count, initial_stop_calls + 1U, + "stop should halt the stepper"); + zassert_equal(fake_stepper_get_actual_position_fake.call_count, + initial_get_pos_calls + 1U, "stop queries actual position once"); + zassert_equal(fake_stepper_drv_disable_fake.call_count, initial_disable_calls + 1U, + "stop should disable the driver once"); +} + +ZTEST(focuser_app, test_initialise_ignores_ealready_from_driver) +{ + assert_stepper_devices_ready(); + ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); + Focuser focuser(stepper); + + int enable_results[] = {-EALREADY, -EALREADY}; + SET_RETURN_SEQ(fake_stepper_drv_enable, enable_results, 2); + + const int ret = focuser.initialise(); + + zassert_equal(ret, 0, "-EALREADY responses should not fail init"); + zassert_equal(fake_stepper_drv_enable_fake.call_count, 2U, + "two enable attempts should have occurred"); +} + +ZTEST_SUITE(focuser_app, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/app/focuser/testcase.yaml b/tests/app/focuser/testcase.yaml new file mode 100644 index 0000000..19afbde --- /dev/null +++ b/tests/app/focuser/testcase.yaml @@ -0,0 +1,7 @@ +common: + tags: focuser + platform_allow: qemu_cortex_m0 + integration_platforms: + - qemu_cortex_m0 +tests: + app.focuser: {} From 2045d455ca594d498759c283f29040103a90d1fc Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Thu, 27 Nov 2025 16:28:57 +0100 Subject: [PATCH 05/11] Refactor thread management and update device configuration structure for stepper and UART handling --- app/CMakeLists.txt | 1 + app/src/Configuration.hpp | 28 ++++++++++++++++---------- app/src/FocuserThread.cpp | 21 +++++++------------- app/src/FocuserThread.hpp | 13 +++--------- app/src/Thread.cpp | 42 +++++++++++++++++++++++++++++++++++++++ app/src/Thread.hpp | 29 +++++++++++++++++++++++++++ app/src/UartThread.cpp | 27 ++++++++----------------- app/src/UartThread.hpp | 12 ++++------- app/src/main.cpp | 8 ++++---- 9 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 app/src/Thread.cpp create mode 100644 app/src/Thread.hpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index eb875d9..18a6bb7 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -15,4 +15,5 @@ target_sources(app PRIVATE src/FocuserThread.cpp src/UartHandler.cpp src/UartThread.cpp + src/Thread.cpp src/ZephyrStepper.cpp) diff --git a/app/src/Configuration.hpp b/app/src/Configuration.hpp index ed35002..0dcdfe4 100644 --- a/app/src/Configuration.hpp +++ b/app/src/Configuration.hpp @@ -9,31 +9,37 @@ namespace config { + namespace devices + { + #if !DT_HAS_CHOSEN(focuser_uart) #error "UART device is required for Moonlite serial protocol" #else - constexpr auto uart = DEVICE_DT_GET(DT_CHOSEN(focuser_uart)); + constexpr auto uart = DEVICE_DT_GET(DT_CHOSEN(focuser_uart)); #endif #if !DT_HAS_CHOSEN(focuser_stepper) #error "Stepper device is required for Moonlite serial protocol" #else - constexpr auto stepper = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper)); + constexpr auto stepper = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper)); #endif #if !DT_HAS_CHOSEN(focuser_stepper_drv) #error "Stepper driver device is required for Moonlite serial protocol" #else - constexpr auto stepper_drv = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper_drv)); + constexpr auto stepper_drv = DEVICE_DT_GET(DT_CHOSEN(focuser_stepper_drv)); #endif - struct ThreadConfig - { - std::size_t stack_size; - int priority; - }; - - inline constexpr ThreadConfig kFocuserThread{CONFIG_FOCUSER_THREAD_STACK_SIZE, K_PRIO_PREEMPT(4)}; - inline constexpr ThreadConfig kSerialThread{CONFIG_SERIAL_THREAD_STACK_SIZE, K_PRIO_PREEMPT(5)}; + } // namespace devices + namespace threads + { + constexpr auto focuser_priority = K_PRIO_PREEMPT(4); + constexpr auto focuser_stack_size = K_THREAD_STACK_LEN(CONFIG_FOCUSER_THREAD_STACK_SIZE); + inline k_thread_stack_t focuser_stack[focuser_stack_size]; + + constexpr auto serial_priority = K_PRIO_PREEMPT(5); + constexpr auto serial_stack_size = K_THREAD_STACK_LEN(CONFIG_SERIAL_THREAD_STACK_SIZE); + inline k_thread_stack_t serial_stack[serial_stack_size]; + } // namespace threads } // namespace config \ No newline at end of file diff --git a/app/src/FocuserThread.cpp b/app/src/FocuserThread.cpp index 64ead35..15cb0c7 100644 --- a/app/src/FocuserThread.cpp +++ b/app/src/FocuserThread.cpp @@ -2,32 +2,25 @@ #include +#include "Configuration.hpp" #include "Focuser.hpp" LOG_MODULE_DECLARE(focuser); FocuserThread::FocuserThread(Focuser &focuser) - : m_focuser(focuser) + : Thread(config::threads::focuser_stack, + K_THREAD_STACK_SIZEOF(config::threads::focuser_stack), + config::threads::focuser_priority, "focuser"), + m_focuser(focuser) { } void FocuserThread::start() { - k_thread_create(&m_thread, m_stack, K_THREAD_STACK_SIZEOF(m_stack), - thread_entry, this, nullptr, nullptr, config::kFocuserThread.priority, - 0, K_NO_WAIT); - k_thread_name_set(&m_thread, "focuser"); -} - -void FocuserThread::thread_entry(void *arg1, void *, void *) -{ - auto *self = static_cast(arg1); - if (self == nullptr) + if (!start_thread()) { - return; + LOG_ERR("Failed to start focuser thread"); } - - self->run(); } void FocuserThread::run() diff --git a/app/src/FocuserThread.hpp b/app/src/FocuserThread.hpp index e4726db..4c499ab 100644 --- a/app/src/FocuserThread.hpp +++ b/app/src/FocuserThread.hpp @@ -1,24 +1,17 @@ #pragma once -#include - -#include - -#include "Configuration.hpp" +#include "Thread.hpp" class Focuser; -class FocuserThread { +class FocuserThread : public Thread { public: explicit FocuserThread(Focuser &focuser); void start(); private: - static void thread_entry(void *, void *, void *); - void run(); + void run() override; Focuser &m_focuser; - k_thread_stack_t m_stack[K_THREAD_STACK_LEN(config::kFocuserThread.stack_size)]; - struct k_thread m_thread; }; diff --git a/app/src/Thread.cpp b/app/src/Thread.cpp new file mode 100644 index 0000000..80f42f0 --- /dev/null +++ b/app/src/Thread.cpp @@ -0,0 +1,42 @@ +#include "Thread.hpp" + +Thread::Thread(k_thread_stack_t *stack_memory, std::size_t stack_size, int priority, const char *name) + : m_stack(stack_memory), m_stack_size(stack_size), m_priority(priority), m_name(name), m_thread{}, + m_started(false) +{ +} + +bool Thread::start_thread() +{ + if (m_started) + { + return true; + } + + if (m_stack == nullptr) + { + return false; + } + + k_thread_create(&m_thread, m_stack, m_stack_size, thread_entry, this, nullptr, nullptr, m_priority, 0, + K_NO_WAIT); + + if (m_name != nullptr) + { + k_thread_name_set(&m_thread, m_name); + } + + m_started = true; + return true; +} + +void Thread::thread_entry(void *arg1, void *, void *) +{ + auto *self = static_cast(arg1); + if (self == nullptr) + { + return; + } + + self->run(); +} diff --git a/app/src/Thread.hpp b/app/src/Thread.hpp new file mode 100644 index 0000000..1e046f0 --- /dev/null +++ b/app/src/Thread.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +class Thread +{ +public: + Thread(k_thread_stack_t *stack_memory, std::size_t stack_size, int priority, const char *name); + virtual ~Thread() = default; + + Thread(const Thread &) = delete; + Thread &operator=(const Thread &) = delete; + +protected: + bool start_thread(); + virtual void run() = 0; + +private: + static void thread_entry(void *, void *, void *); + + k_thread_stack_t *m_stack; + std::size_t m_stack_size; + int m_priority; + const char *m_name; + struct k_thread m_thread; + bool m_started; +}; diff --git a/app/src/UartThread.cpp b/app/src/UartThread.cpp index 3482d4d..8556a7a 100644 --- a/app/src/UartThread.cpp +++ b/app/src/UartThread.cpp @@ -5,37 +5,26 @@ #include #include +#include "Configuration.hpp" #include "Focuser.hpp" #include "UartHandler.hpp" LOG_MODULE_REGISTER(uart_thread, CONFIG_APP_LOG_LEVEL); -namespace -{ -K_THREAD_STACK_DEFINE(serial_stack, config::kSerialThread.stack_size); -} - UartThread::UartThread(Focuser &focuser, UartHandler &uart_handler) - : m_parser(focuser), m_uart_handler(uart_handler) + : Thread(config::threads::serial_stack, + K_THREAD_STACK_SIZEOF(config::threads::serial_stack), + config::threads::serial_priority, "uart"), + m_parser(focuser), m_uart_handler(uart_handler) { } -void UartThread::start(int priority) +void UartThread::start() { - k_thread_create(&m_thread, serial_stack, K_THREAD_STACK_SIZEOF(serial_stack), - thread_entry, this, nullptr, nullptr, priority, 0, K_NO_WAIT); - k_thread_name_set(&m_thread, "serial"); -} - -void UartThread::thread_entry(void *arg1, void *, void *) -{ - auto *self = static_cast(arg1); - if (self == nullptr) + if (!start_thread()) { - return; + LOG_ERR("Failed to start UART thread"); } - - self->run(); } void UartThread::run() diff --git a/app/src/UartThread.hpp b/app/src/UartThread.hpp index a6e30df..967c358 100644 --- a/app/src/UartThread.hpp +++ b/app/src/UartThread.hpp @@ -1,27 +1,23 @@ #pragma once -#include - #include -#include "Configuration.hpp" +#include "Thread.hpp" class Focuser; class UartHandler; -class UartThread { +class UartThread : public Thread { public: UartThread(Focuser &focuser, UartHandler &uart_handler); - void start(int priority); + void start(); private: - static void thread_entry(void *, void *, void *); - void run(); + void run() override; static constexpr std::size_t kMaxLoggedFrameLen = 80U; moonlite::Parser m_parser; UartHandler &m_uart_handler; - k_thread m_thread; }; diff --git a/app/src/main.cpp b/app/src/main.cpp index aa90ae7..3bf6f0f 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -23,10 +23,10 @@ LOG_MODULE_REGISTER(focuser, CONFIG_APP_LOG_LEVEL); namespace { - ZephyrFocuserStepper g_stepper_adapter(config::stepper, config::stepper_drv); + ZephyrFocuserStepper g_stepper_adapter(config::devices::stepper, config::devices::stepper_drv); Focuser g_focuser(g_stepper_adapter); FocuserThread g_focuser_thread(g_focuser); - UartHandler g_uart_handler(config::uart); + UartHandler g_uart_handler(config::devices::uart); UartThread g_uart_thread(g_focuser, g_uart_handler); } // namespace @@ -35,7 +35,7 @@ int main(void) { LOG_INF("Moonlite focuser firmware %s", APP_VERSION_STRING); - if (!device_is_ready(config::uart)) + if (!device_is_ready(config::devices::uart)) { LOG_ERR("UART handler device not ready"); return -ENODEV; @@ -57,7 +57,7 @@ int main(void) g_focuser_thread.start(); - g_uart_thread.start(config::kSerialThread.priority); + g_uart_thread.start(); LOG_INF("Moonlite focuser ready: UART 9600 8N1"); From bbdade658cb663f242fddc02e541e198a69b6efd Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Fri, 28 Nov 2025 21:22:19 +0100 Subject: [PATCH 06/11] Refactor focuser initialization to include firmware version and remove unused thread stack size configurations --- app/Kconfig | 12 ------------ app/src/Configuration.hpp | 7 +++++-- app/src/Focuser.cpp | 13 ++++--------- app/src/Focuser.hpp | 4 +++- app/src/main.cpp | 2 +- tests/app/focuser/Kconfig | 11 +++++++++++ tests/app/focuser/boards/qemu_cortex_m0.conf | 3 +-- tests/app/focuser/prj.conf | 2 ++ tests/app/focuser/src/main.cpp | 11 ++++++----- 9 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 tests/app/focuser/Kconfig diff --git a/app/Kconfig b/app/Kconfig index baff6b5..929c373 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -12,18 +12,6 @@ endmenu menu "OpenAstroFocuser options" -config FOCUSER_THREAD_STACK_SIZE - int "Focuser thread stack size" - default 2048 - help - Stack allocation used by the focuser control thread. - -config SERIAL_THREAD_STACK_SIZE - int "Serial thread stack size" - default 2048 - help - Stack allocation used by the Moonlite UART handler thread. - endmenu module = APP diff --git a/app/src/Configuration.hpp b/app/src/Configuration.hpp index 0dcdfe4..49fb1df 100644 --- a/app/src/Configuration.hpp +++ b/app/src/Configuration.hpp @@ -4,10 +4,13 @@ #include #include +#include + #include namespace config { + constexpr const char version[] = STRINGIFY(APP_VERSION_MAJOR) STRINGIFY(APP_VERSION_MINOR); namespace devices { @@ -35,11 +38,11 @@ namespace config namespace threads { constexpr auto focuser_priority = K_PRIO_PREEMPT(4); - constexpr auto focuser_stack_size = K_THREAD_STACK_LEN(CONFIG_FOCUSER_THREAD_STACK_SIZE); + constexpr auto focuser_stack_size = K_THREAD_STACK_LEN(2048); inline k_thread_stack_t focuser_stack[focuser_stack_size]; constexpr auto serial_priority = K_PRIO_PREEMPT(5); - constexpr auto serial_stack_size = K_THREAD_STACK_LEN(CONFIG_SERIAL_THREAD_STACK_SIZE); + constexpr auto serial_stack_size = K_THREAD_STACK_LEN(2048); inline k_thread_stack_t serial_stack[serial_stack_size]; } // namespace threads } // namespace config \ No newline at end of file diff --git a/app/src/Focuser.cpp b/app/src/Focuser.cpp index 360091f..a5aff56 100644 --- a/app/src/Focuser.cpp +++ b/app/src/Focuser.cpp @@ -2,17 +2,12 @@ #include -#include - #include LOG_MODULE_DECLARE(focuser, CONFIG_APP_LOG_LEVEL); namespace { - - constexpr const char kFirmwareVersion[] = STRINGIFY(APP_VERSION_MAJOR) STRINGIFY(APP_VERSION_MINOR); - uint32_t compute_step_period_us(uint8_t multiplier) { uint32_t m = (multiplier == 0U) ? 1U : static_cast(multiplier); @@ -27,8 +22,8 @@ namespace } // namespace -Focuser::Focuser(FocuserStepper &stepper) - : m_stepper(stepper) +Focuser::Focuser(FocuserStepper &stepper, const char *firmware_version) + : m_firmware_version(firmware_version), m_stepper(stepper) { (void)set_stepper_driver_enabled(true); } @@ -242,8 +237,8 @@ bool Focuser::isMoving() std::string Focuser::getFirmwareVersion() { LOG_DBG("getFirmwareVersion()"); - LOG_DBG("getFirmwareVersion -> %s", kFirmwareVersion); - return std::string(kFirmwareVersion); + LOG_DBG("getFirmwareVersion -> %s", m_firmware_version); + return std::string(m_firmware_version); } uint8_t Focuser::getSpeed() diff --git a/app/src/Focuser.hpp b/app/src/Focuser.hpp index a0c4aea..9284153 100644 --- a/app/src/Focuser.hpp +++ b/app/src/Focuser.hpp @@ -12,7 +12,7 @@ class Focuser final : public moonlite::Handler { public: - explicit Focuser(FocuserStepper &stepper); + explicit Focuser(FocuserStepper &stepper, const char *firmware_version); int initialise(); void loop(); @@ -71,6 +71,8 @@ class Focuser final : public moonlite::Handler int32_t read_actual_position(); int set_stepper_driver_enabled(bool enable); + const char *m_firmware_version; + FocuserState m_state{}; FocuserStepper &m_stepper; }; diff --git a/app/src/main.cpp b/app/src/main.cpp index 3bf6f0f..5ef7026 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -24,7 +24,7 @@ namespace { ZephyrFocuserStepper g_stepper_adapter(config::devices::stepper, config::devices::stepper_drv); - Focuser g_focuser(g_stepper_adapter); + Focuser g_focuser(g_stepper_adapter, config::version); FocuserThread g_focuser_thread(g_focuser); UartHandler g_uart_handler(config::devices::uart); UartThread g_uart_thread(g_focuser, g_uart_handler); diff --git a/tests/app/focuser/Kconfig b/tests/app/focuser/Kconfig new file mode 100644 index 0000000..e8f7dac --- /dev/null +++ b/tests/app/focuser/Kconfig @@ -0,0 +1,11 @@ +menu "Zephyr" +source "Kconfig.zephyr" +endmenu + +menu "OpenAstroFocuser test options" + +endmenu + +module = APP +module-str = APP +source "subsys/logging/Kconfig.template.log_config" diff --git a/tests/app/focuser/boards/qemu_cortex_m0.conf b/tests/app/focuser/boards/qemu_cortex_m0.conf index b3a011c..f6217ec 100644 --- a/tests/app/focuser/boards/qemu_cortex_m0.conf +++ b/tests/app/focuser/boards/qemu_cortex_m0.conf @@ -7,5 +7,4 @@ CONFIG_GPIO=y CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_MAIN_STACK_SIZE=2048 -CONFIG_FOCUSER_THREAD_STACK_SIZE=1536 -CONFIG_SERIAL_THREAD_STACK_SIZE=1536 +CONFIG_QEMU_ICOUNT=y \ No newline at end of file diff --git a/tests/app/focuser/prj.conf b/tests/app/focuser/prj.conf index 2d06dd2..f0c96f5 100644 --- a/tests/app/focuser/prj.conf +++ b/tests/app/focuser/prj.conf @@ -12,3 +12,5 @@ CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_PRINTK=y CONFIG_STEPPER=y CONFIG_FAKE_STEPPER=y + +CONFIG_APP_LOG_LEVEL_DBG=y \ No newline at end of file diff --git a/tests/app/focuser/src/main.cpp b/tests/app/focuser/src/main.cpp index f26b81c..039e389 100644 --- a/tests/app/focuser/src/main.cpp +++ b/tests/app/focuser/src/main.cpp @@ -25,6 +25,7 @@ namespace const struct device *const k_stepper_controller = DEVICE_DT_GET(DT_ALIAS(stepper)); const struct device *const k_stepper_driver = DEVICE_DT_GET(DT_ALIAS(stepper_drv)); +constexpr char kFirmwareVersion[] = "twister-test"; void assert_stepper_devices_ready() { @@ -92,7 +93,7 @@ ZTEST(focuser_app, test_initialise_requires_ready_stepper) assert_stepper_devices_ready(); ZephyrFocuserStepper hw_stepper(k_stepper_controller, k_stepper_driver); ReadyOverrideStepper stepper(hw_stepper, false); - Focuser focuser(stepper); + Focuser focuser(stepper, kFirmwareVersion); const int ret = focuser.initialise(); @@ -109,7 +110,7 @@ ZTEST(focuser_app, test_initialise_configures_stepper_when_ready) { assert_stepper_devices_ready(); ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); - Focuser focuser(stepper); + Focuser focuser(stepper, kFirmwareVersion); const int ret = focuser.initialise(); @@ -132,7 +133,7 @@ ZTEST(focuser_app, test_set_speed_clamps_to_minimum_and_updates_interval) { assert_stepper_devices_ready(); ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); - Focuser focuser(stepper); + Focuser focuser(stepper, kFirmwareVersion); zassert_ok(focuser.initialise(), "initialise precondition"); const unsigned int initial_microstep_calls = @@ -157,7 +158,7 @@ ZTEST(focuser_app, test_stop_stops_motion_and_disables_driver) { assert_stepper_devices_ready(); ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); - Focuser focuser(stepper); + Focuser focuser(stepper, kFirmwareVersion); zassert_ok(focuser.initialise(), "initialise precondition"); const unsigned int initial_stop_calls = fake_stepper_stop_fake.call_count; @@ -187,7 +188,7 @@ ZTEST(focuser_app, test_initialise_ignores_ealready_from_driver) { assert_stepper_devices_ready(); ZephyrFocuserStepper stepper(k_stepper_controller, k_stepper_driver); - Focuser focuser(stepper); + Focuser focuser(stepper, kFirmwareVersion); int enable_results[] = {-EALREADY, -EALREADY}; SET_RETURN_SEQ(fake_stepper_drv_enable, enable_results, 2); From ab9ea2a2459dd557b4165b2c01ff5ec9a45fff8b Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 29 Nov 2025 13:13:25 +0100 Subject: [PATCH 07/11] Add setup action for Zephyr project and update build workflow to use it --- .github/actions/setup-zephyr/action.yml | 252 ++++++++++++++++++++++++ .github/workflows/build.yml | 47 +---- 2 files changed, 262 insertions(+), 37 deletions(-) create mode 100644 .github/actions/setup-zephyr/action.yml diff --git a/.github/actions/setup-zephyr/action.yml b/.github/actions/setup-zephyr/action.yml new file mode 100644 index 0000000..74738ca --- /dev/null +++ b/.github/actions/setup-zephyr/action.yml @@ -0,0 +1,252 @@ +name: Setup Zephyr project +description: Setup a Zephyr base project using west and downloading the Zephyr SDK + +inputs: + app-path: + description: | + Application code path, should contain a west.yml file if + "manifest-file-name" is not specified, cannot contain multiple + directories (use base-path instead) + required: true + + base-path: + description: | + Application base path, should contain the app-path. Defaults to "." if + unspecified + required: false + default: . + + ccache-max-size: + description: | + Maximum size of the files stored in the compiler cache (ccache). Defaults to 512MB. + required: false + default: 512MB + + enable-ccache: + description: | + Enable caching/restoring of compiler cache (ccache) files between invocations. + Defaults to 'true'. + required: false + default: true + + ccache-cache-key: + description: | + An additional string used in the ccache cache key, use it to identify the + cache in a way that makes sense for compiled object cache reuse between + different build workflows. Default to "default". + required: false + default: default + + manifest-file-name: + description: Name of the west workspace manifest file name in "app-path" + required: false + default: west.yml + + sdk-version: + description: Zephyr SDK version to use or "auto" to detect automatically + required: false + default: auto + + sdk-base: + description: Base URL of the Zephyr SDK + required: false + default: https://github.com/zephyrproject-rtos/sdk-ng/releases/download + + toolchains: + description: List of toolchains to install, colon separated + required: false + default: arm-zephyr-eabi + + west-project-filter: + description: West project filter + required: false + default: "" + + west-group-filter: + description: West group filter + required: false + default: "" + + west-version: + description: West version to install, latest if omitted + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Install dependencies + shell: bash + run: | + if [ "${{ runner.os }}" = "Windows" ]; then + python.exe -m pip install -U pip + fi + + pip3 install -U pip wheel + + if [ -n "${{ inputs.west-version }}" ]; then + pip3 install west==${{ inputs.west-version }} + else + pip3 install west + fi + + if [ "${{ runner.os }}" = "Linux" ]; then + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update -y + sudo apt-get install -y ninja-build ccache gperf + if [ "${{ runner.arch }}" = "X64" ]; then + sudo apt-get install -y libc6-dev-i386 g++-multilib + fi + elif [ "${{ runner.os }}" = "macOS" ]; then + brew install ninja ccache qemu dtc gperf + elif [ "${{ runner.os }}" = "Windows" ]; then + choco feature enable -n allowGlobalConfirmation + choco install ninja wget gperf + fi + + - name: Initialize + working-directory: ${{ inputs.base-path }} + shell: bash + run: | + west init -l ${{ inputs.app-path }} --mf ${{ inputs.manifest-file-name }} + if [ -n "${{ inputs.west-group-filter }}" ]; then + west config manifest.group-filter -- ${{ inputs.west-group-filter }} + fi + if [ -n "${{ inputs.west-project-filter }}" ]; then + west config manifest.project-filter -- ${{ inputs.west-project-filter }} + fi + west update -o=--depth=1 -n + + - name: Environment setup + working-directory: ${{ inputs.base-path }} + id: env-setup + shell: bash + run: | + runner="${{ runner.os }}-${{ runner.arch }}" + if [ "$runner" = "Linux-X64" ]; then + sdk_variant="linux-x86_64" + elif [ "$runner" = "Linux-ARM64" ]; then + sdk_variant="linux-aarch64" + elif [ "$runner" = "macOS-X64" ]; then + sdk_variant="macos-x86_64" + elif [ "$runner" = "macOS-ARM64" ]; then + sdk_variant="macos-aarch64" + elif [ "$runner" = "Windows-X64" ]; then + sdk_variant="windows-x86_64" + else + echo "Unsupported runner platform: $runner" + fi + + if [ "${{ runner.os }}" = "Linux" ]; then + pip_cache_path="~/.cache/pip" + ccache_path="$HOME/.cache/ccache" + sdk_ext="tar.xz" + setup_file="./setup.sh" + setup_opt="-" + elif [ "${{ runner.os }}" = "macOS" ]; then + pip_cache_path="~/Library/Caches/pip" + ccache_path="$HOME/Library/Caches/ccache" + sdk_ext="tar.xz" + setup_file="./setup.sh" + setup_opt="-" + elif [ "${{ runner.os }}" = "Windows" ]; then + pip_cache_path="~/AppData/Local/pip/Cache" + ccache_path="$HOME/AppData/Local/ccache/Cache" + sdk_ext="7z" + setup_file="./setup.cmd" + setup_opt="//" + fi + + if [ "${{ inputs.sdk-version }}" = "auto" ]; then + zephyr_path="zephyr" + if west list -f '{abspath}' zephyr; then + zephyr_path="$( west list -f '{abspath}' zephyr )" + fi + + if [ -f "${zephyr_path}/SDK_VERSION" ]; then + echo "Reading SDK version from ${zephyr_path}/SDK_VERSION" + sdk_version=$( cat ${zephyr_path}/SDK_VERSION ) + else + echo "Cannot find ${zephyr_path}/SDK_VERSION" + exit 1 + fi + else + sdk_version="${{ inputs.sdk-version }}" + fi + + echo "SDK_VERSION=${sdk_version}" >> $GITHUB_ENV + echo "SDK_FILE=zephyr-sdk-${sdk_version}_${sdk_variant}_minimal.${sdk_ext}" >> $GITHUB_ENV + echo "PIP_CACHE_PATH=${pip_cache_path}" >> $GITHUB_ENV + echo "CCACHE_TIMESTAMP=$(date +%s)" >> $GITHUB_ENV + echo "CCACHE_DIR=${ccache_path}" >> $GITHUB_ENV + echo "CCACHE_IGNOREOPTIONS=-specs=* --specs=*" >> $GITHUB_ENV + echo "SETUP_FILE=${setup_file}" >> $GITHUB_ENV + echo "SETUP_OPT=${setup_opt}" >> $GITHUB_ENV + + - name: Cache Python packages + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ env.PIP_CACHE_PATH }} + key: pip-${{ runner.os }}-${{ hashFiles(format('{0}/zephyr/scripts/requirements*.txt', inputs.base-path)) }} + + - name: Install Python packages + working-directory: ${{ inputs.base-path }} + shell: bash + # The direct use of pip3 should be removed after zephyr 4.1 has been phased out, and the west + # command should only be used instead. + run: | + west packages pip --install --ignore-venv-check || pip3 install -r zephyr/scripts/requirements.txt + + - name: Cache Zephyr SDK + id: cache-toolchain + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ inputs.base-path }}/zephyr-sdk + key: ${{ env.SDK_FILE }}-${{ github.event.repository.name }}-${{ inputs.toolchains }} + + - if: ${{ steps.cache-toolchain.outputs.cache-hit != 'true' }} + working-directory: ${{ inputs.base-path }} + name: Download Zephyr SDK + shell: bash + run: | + wget --progress=dot:giga ${{ inputs.sdk-base }}/v${SDK_VERSION}/${SDK_FILE} + rm -rf zephyr-sdk + if [ "${{ runner.os }}" = "Windows" ]; then + 7z x $SDK_FILE + mv zephyr-sdk-${SDK_VERSION} zephyr-sdk + else + mkdir zephyr-sdk + tar xvf $SDK_FILE -C zephyr-sdk --strip-components=1 + fi + rm -f $SDK_FILE + + - name: Setup Zephyr SDK + working-directory: ${{ inputs.base-path }}/zephyr-sdk + shell: bash + run: | + IFS=":" + TOOLCHAINS="${{ inputs.toolchains }}" + for toolchain in $TOOLCHAINS; do + ${SETUP_FILE} ${SETUP_OPT}t $toolchain + done + if [ ! -d sysroots ]; then + ${SETUP_FILE} ${SETUP_OPT}h + fi + ${SETUP_FILE} ${SETUP_OPT}c + + - name: Cache ccache data + if: inputs.enable-ccache == 'true' && runner.os != 'Windows' + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ env.CCACHE_DIR }} + key: ccache-${{ inputs.ccache-cache-key }}-${{ env.CCACHE_TIMESTAMP }} + restore-keys: ccache-${{ inputs.ccache-cache-key }}- + + - name: Set up ccache + if: inputs.enable-ccache == 'true' && runner.os != 'Windows' + shell: bash + run: | + mkdir -p ${{ env.CCACHE_DIR }} + ccache -M ${{ inputs.ccache-max-size }} + ccache -p + ccache -z -s -vv \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36a08c9..f3e4717 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - path: OpenAstroFocuser + path: example-application persist-credentials: false - name: Set up Python @@ -29,42 +29,15 @@ jobs: with: python-version: 3.12 - - name: Install system dependencies - shell: bash - run: | - if [ "${{ runner.os }}" = "Linux" ]; then - sudo rm -f /var/lib/man-db/auto-update - sudo apt-get update -y - sudo apt-get install -y ninja-build ccache gperf - if [ "${{ runner.arch }}" = "X64" ]; then - sudo apt-get install -y libc6-dev-i386 g++-multilib - fi - elif [ "${{ runner.os }}" = "macOS" ]; then - brew install ninja ccache qemu dtc gperf - elif [ "${{ runner.os }}" = "Windows" ]; then - choco feature enable -n allowGlobalConfirmation - choco install ninja wget gperf - fi - - - name: Install west - run: | - pip install west - - - name: Initialize Zephyr workspace - run: | - west init -l OpenAstroFocuser - west update - - - name: Install pip dependencies - run: | - west packages pip --install --ignore-venv-check || pip3 install -r zephyr/scripts/requirements.txt - - - name: Install Zephyr SDK - run: | - west sdk install -t arm-zephyr-eabi xtensa-espressif_esp32s3_zephyr-elf + - name: Setup Zephyr project + uses: actions/setup-zephyr + with: + app-path: example-application + toolchains: arm-zephyr-eabi,xtensa-espressif_esp32s3_zephyr-elf + ccache-cache-key: ${{ matrix.os }} - name: Build firmware - working-directory: OpenAstroFocuser + working-directory: example-application shell: bash run: | if [ "${{ runner.os }}" = "Windows" ]; then @@ -73,10 +46,10 @@ jobs: west twister -T app -v --inline-logs --integration $EXTRA_TWISTER_FLAGS - name: Twister Tests - working-directory: OpenAstroFocuser + working-directory: example-application shell: bash run: | if [ "${{ runner.os }}" = "Windows" ]; then EXTRA_TWISTER_FLAGS="--short-build-path -O/tmp/twister-out" fi - west twister -T tests -v --inline-logs --integration $EXTRA_TWISTER_FLAGS + west twister -T tests -v --inline-logs --integration $EXTRA_TWISTER_FLAGS \ No newline at end of file From d7ce4b1ca1545bc54331c25fb098ad6736cacaac Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 29 Nov 2025 13:14:55 +0100 Subject: [PATCH 08/11] Fix setup action path for Zephyr project in build workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3e4717..fbdeafb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: python-version: 3.12 - name: Setup Zephyr project - uses: actions/setup-zephyr + uses: ./.github/actions/setup-zephyr with: app-path: example-application toolchains: arm-zephyr-eabi,xtensa-espressif_esp32s3_zephyr-elf From b4c04b172deb04d563075381e3aea710f0421571 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 29 Nov 2025 13:39:16 +0100 Subject: [PATCH 09/11] Remove unnecessary path specification in checkout step of build workflow --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbdeafb..36db63c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - path: example-application persist-credentials: false - name: Set up Python From cc324b6d50db55da0132136fc577c9320258d108 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 29 Nov 2025 14:09:25 +0100 Subject: [PATCH 10/11] Update build workflow to use correct paths for Zephyr project and firmware build --- .github/workflows/build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36db63c..eb7557d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + path: OpenAstroFocuser persist-credentials: false - name: Set up Python @@ -29,14 +30,14 @@ jobs: python-version: 3.12 - name: Setup Zephyr project - uses: ./.github/actions/setup-zephyr + uses: ./OpenAstroFocuser/.github/actions/setup-zephyr with: - app-path: example-application + app-path: OpenAstroFocuser toolchains: arm-zephyr-eabi,xtensa-espressif_esp32s3_zephyr-elf ccache-cache-key: ${{ matrix.os }} - name: Build firmware - working-directory: example-application + working-directory: OpenAstroFocuser shell: bash run: | if [ "${{ runner.os }}" = "Windows" ]; then @@ -45,7 +46,7 @@ jobs: west twister -T app -v --inline-logs --integration $EXTRA_TWISTER_FLAGS - name: Twister Tests - working-directory: example-application + working-directory: OpenAstroFocuser shell: bash run: | if [ "${{ runner.os }}" = "Windows" ]; then From 07adbcac0d49a2d1e287200b61ddeac9db5e2b93 Mon Sep 17 00:00:00 2001 From: Andre Stefanov Date: Sat, 29 Nov 2025 14:12:05 +0100 Subject: [PATCH 11/11] Fix toolchain specification format in Zephyr setup action --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb7557d..bdab661 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: uses: ./OpenAstroFocuser/.github/actions/setup-zephyr with: app-path: OpenAstroFocuser - toolchains: arm-zephyr-eabi,xtensa-espressif_esp32s3_zephyr-elf + toolchains: arm-zephyr-eabi:xtensa-espressif_esp32s3_zephyr-elf ccache-cache-key: ${{ matrix.os }} - name: Build firmware