#include #include #include #include #include #include #include "track_core/memory_strip.hpp" #include "track_core/render.hpp" #include "track_core/scheme_runtime.hpp" namespace nb = nanobind; using namespace nb::literals; namespace { void validate_render_config(const track_core::TrackConfig &config) { if (!config.verify() || config.line_leds_num == 0) { throw nb::value_error("invalid TrackConfig"); } if (config.draw_kind == track_core::TrackDrawKind::circular && config.active_line_length_m >= config.line_length_m) { throw nb::value_error("circular tracks require active_line_length_m < line_length_m"); } } nb::list colors_to_list(std::span colors) { nb::list result; for (const auto &color : colors) { result.append(color); } return result; } nb::list spans_to_list(const track_core::TrackRenderPlan &plan) { nb::list result; for (std::size_t i = 0; i < plan.span_count; ++i) { result.append(plan.spans[i]); } return result; } track_core::TrackRenderPlan make_render_plan_checked( const track_core::TrackConfig &config, const track_core::TrackInfo &info, const track_core::TrackReport &report) { validate_render_config(config); return track_core::make_track_render_plan(config, info, report); } track_core::SchemeTrackRuntime make_scheme_track_runtime_checked(track_core::DecodedScheme scheme) { auto result = track_core::make_scheme_track_runtime(std::move(scheme)); if (!result) { throw std::runtime_error("invalid scheme"); } return std::move(*result); } void add_scheme_checked(track_core::SchemeTrainingRuntime &runtime, track_core::DecodedScheme scheme) { const auto result = runtime.add_scheme(std::move(scheme)); if (!result) { throw std::runtime_error("invalid scheme"); } } nb::list runtime_state_collection(const track_core::SchemeTrainingRuntime &runtime) { nb::list result; for (const auto &report : runtime.state_collection().states) { result.append(report); } return result; } nb::list runtime_scheme_status(const track_core::SchemeTrainingRuntime &runtime) { nb::list result; for (const auto &status : runtime.scheme_status().scheme_status) { result.append(status); } return result; } nb::list runtime_render_pixels( const track_core::SchemeTrainingRuntime &runtime, const track_core::TrackConfig &config) { const auto pixels = runtime.render_pixels(config); if (!pixels) { throw std::runtime_error("failed to render runtime pixels"); } return colors_to_list(*pixels); } nb::list render_pixels( const track_core::TrackConfig &config, const track_core::TrackInfo &info, const track_core::TrackReport &report) { validate_render_config(config); track_core::MemoryStrip strip(config.line_leds_num); const auto plan = track_core::make_track_render_plan(config, info, report); const auto err = track_core::apply_render_plan(strip, plan); if (err != track_core::TrackError::ok) { throw std::runtime_error("failed to apply render plan"); } return colors_to_list(strip.pixels()); } std::string color_repr(const track_core::Color &color) { return "Color(" + std::to_string(color.r) + ", " + std::to_string(color.g) + ", " + std::to_string(color.b) + ")"; } } // namespace NB_MODULE(_core, m) { m.doc() = "Python bindings for the platform-neutral track-core library"; nb::enum_(m, "TrackError") .value("ok", track_core::TrackError::ok) .value("invalid_arg", track_core::TrackError::invalid_arg) .value("invalid_size", track_core::TrackError::invalid_size) .value("invalid_state", track_core::TrackError::invalid_state) .value("not_supported", track_core::TrackError::not_supported) .value("range", track_core::TrackError::range); nb::enum_(m, "TrackDrawKind") .value("circular", track_core::TrackDrawKind::circular) .value("linear", track_core::TrackDrawKind::linear); nb::enum_(m, "SchemeKind") .value("speed_input_mileage_segmented_time_free", track_core::SchemeKind::speed_input_mileage_segmented_time_free) .value("mileage_input_time_segmented_speed_free", track_core::SchemeKind::mileage_input_time_segmented_speed_free) .value("speed_input_time_segmented_mileage_free", track_core::SchemeKind::speed_input_time_segmented_mileage_free) .value("repeated_speed_input_mileage_segmentation_input_time_segmented", track_core::SchemeKind::repeated_speed_input_mileage_segmentation_input_time_segmented); nb::enum_(m, "AccelerationProfile") .value("instant", track_core::AccelerationProfile::instant) .value("smooth", track_core::AccelerationProfile::smooth); nb::enum_(m, "TrackState") .value("stop", track_core::TrackState::stop) .value("run", track_core::TrackState::run) .value("test_rainbow", track_core::TrackState::test_rainbow) .value("test_blink", track_core::TrackState::test_blink); nb::class_(m, "Color") .def(nb::init<>()) .def(nb::init(), "r"_a, "g"_a, "b"_a) .def(nb::init(), "value"_a) .def_rw("r", &track_core::Color::r) .def_rw("g", &track_core::Color::g) .def_rw("b", &track_core::Color::b) .def("hex", &track_core::Color::hex) .def_prop_ro("value", [](const track_core::Color &color) { return static_cast(color); }) .def_static("black", &track_core::Color::black) .def_static("red", &track_core::Color::red) .def_static("orange", &track_core::Color::orange) .def_static("yellow", &track_core::Color::yellow) .def_static("green", &track_core::Color::green) .def_static("cyan", &track_core::Color::cyan) .def_static("blue", &track_core::Color::blue) .def_static("indigo", &track_core::Color::indigo) .def_static("violet", &track_core::Color::violet) .def_static("white", &track_core::Color::white) .def("__eq__", [](const track_core::Color &lhs, const track_core::Color &rhs) { return lhs == rhs; }) .def("__repr__", &color_repr); nb::class_(m, "TrackConfig") .def(nb::init<>()) .def_rw("draw_kind", &track_core::TrackConfig::draw_kind) .def_rw("line_length_m", &track_core::TrackConfig::line_length_m) .def_rw("active_line_length_m", &track_core::TrackConfig::active_line_length_m) .def_rw("head_offset_m", &track_core::TrackConfig::head_offset_m) .def_rw("line_leds_num", &track_core::TrackConfig::line_leds_num) .def("led_distance", &track_core::TrackConfig::led_distance) .def("verify", &track_core::TrackConfig::verify) .def_static("default_config", &track_core::TrackConfig::default_config); nb::class_(m, "TrackInfo") .def(nb::init<>()) .def_rw("kind", &track_core::TrackInfo::kind) .def_rw("color", &track_core::TrackInfo::color) .def_rw("id", &track_core::TrackInfo::id) .def_rw("is_running", &track_core::TrackInfo::is_running) .def_rw("num_segments", &track_core::TrackInfo::num_segments); nb::class_(m, "TrackSchemeStatus") .def(nb::init<>()) .def_rw("id", &track_core::TrackSchemeMgrRead::Status::id) .def_rw("segment_count", &track_core::TrackSchemeMgrRead::Status::segment_count) .def_rw("kind", &track_core::TrackSchemeMgrRead::Status::kind); nb::class_(m, "TrackReport") .def(nb::init<>()) .def_rw("id", &track_core::TrackReport::id) .def_rw("state", &track_core::TrackReport::state) .def_rw("mileage_m", &track_core::TrackReport::mileage_m) .def_rw("speed_m_s", &track_core::TrackReport::speed_m_s) .def_rw("time_elapsed_ms", &track_core::TrackReport::time_elapsed_ms); nb::class_(m, "TrackRenderSpan") .def(nb::init<>()) .def_rw("start_led", &track_core::TrackRenderSpan::start_led) .def_rw("led_count", &track_core::TrackRenderSpan::led_count) .def_rw("color", &track_core::TrackRenderSpan::color); nb::class_(m, "TrackRenderPlan") .def(nb::init<>()) .def("empty", &track_core::TrackRenderPlan::empty) .def_prop_ro("span_count", [](const track_core::TrackRenderPlan &plan) { return plan.span_count; }) .def_prop_ro("spans", &spans_to_list); nb::class_(m, "MemoryStrip") .def(nb::init(), "led_count"_a = 0) .def("begin", &track_core::MemoryStrip::begin) .def("clear", &track_core::MemoryStrip::clear) .def("fill", &track_core::MemoryStrip::fill, "start"_a, "count"_a, "color"_a) .def("show", &track_core::MemoryStrip::show) .def("set_leds_count", &track_core::MemoryStrip::set_leds_count, "count"_a) .def_prop_ro("leds_count", &track_core::MemoryStrip::leds_count) .def_prop_ro("frame_sequence", &track_core::MemoryStrip::frame_sequence) .def_prop_ro("pixels", [](const track_core::MemoryStrip &strip) { return colors_to_list(strip.pixels()); }); nb::class_(m, "SMSegment") .def(nb::init<>()) .def_rw("speed_m_s", &track_core::SMSegment::speed_m_s) .def_rw("mileage_from_start_m", &track_core::SMSegment::mileage_from_start_m); nb::class_(m, "MTSegment") .def(nb::init<>()) .def_rw("mileage_to_travel_this_segment_m", &track_core::MTSegment::mileage_to_travel_this_segment_m) .def_rw("time_since_start_s", &track_core::MTSegment::time_since_start_s); nb::class_(m, "STSegment") .def(nb::init<>()) .def_rw("speed_m_s", &track_core::STSegment::speed_m_s) .def_rw("time_since_start_s", &track_core::STSegment::time_since_start_s); nb::class_(m, "RepeatedSMSegment") .def(nb::init<>()) .def_rw("speed_mileage_segments", &track_core::RepeatedSMSegment::speed_mileage_segments) .def_rw("time_since_start_s", &track_core::RepeatedSMSegment::time_since_start_s); nb::class_(m, "DecodedScheme") .def_prop_ro("id", [](const track_core::DecodedScheme &scheme) { return scheme.id; }) .def_prop_ro("color", [](const track_core::DecodedScheme &scheme) { return scheme.color; }) .def_prop_ro("kind", [](const track_core::DecodedScheme &scheme) { return scheme.kind; }) .def_prop_ro("acceleration_profile", [](const track_core::DecodedScheme &scheme) { return scheme.acceleration_profile; }); nb::class_(m, "SchemeTrackState") .def(nb::init<>()) .def_rw("is_running", &track_core::SchemeTrackState::is_running) .def_rw("primary_segment_index", &track_core::SchemeTrackState::primary_segment_index) .def_rw("sub_segment_index", &track_core::SchemeTrackState::sub_segment_index) .def_rw("mileage_m", &track_core::SchemeTrackState::mileage_m) .def_rw("loop_mileage_m", &track_core::SchemeTrackState::loop_mileage_m) .def_rw("speed_m_s", &track_core::SchemeTrackState::speed_m_s) .def_rw("elapsed_s", &track_core::SchemeTrackState::elapsed_s); nb::class_(m, "SchemeTrackRuntime") .def_prop_ro("state", [](const track_core::SchemeTrackRuntime &runtime) { return runtime.state; }) .def("info", &track_core::scheme_track_info) .def("report", &track_core::scheme_track_report); nb::class_(m, "SchemeTrainingRuntime") .def(nb::init<>()) .def("has_program", &track_core::SchemeTrainingRuntime::has_program) .def("all_stopped", &track_core::SchemeTrainingRuntime::all_stopped) .def("add_scheme", &add_scheme_checked, "scheme"_a) .def("clear", &track_core::SchemeTrainingRuntime::clear) .def("start", &track_core::SchemeTrainingRuntime::start) .def("stop", &track_core::SchemeTrainingRuntime::stop) .def("tick", &track_core::SchemeTrainingRuntime::tick, "config"_a, "delta_s"_a) .def("state_collection", &runtime_state_collection) .def("scheme_status", &runtime_scheme_status) .def("render_pixels", &runtime_render_pixels, "config"_a); m.def("make_render_plan", &make_render_plan_checked, "config"_a, "info"_a, "report"_a); m.def("render_pixels", &render_pixels, "config"_a, "info"_a, "report"_a); m.def("make_speed_mileage_scheme", &track_core::make_speed_mileage_scheme, "id"_a, "color"_a, "acceleration_profile"_a, "segments"_a); m.def("make_mileage_time_scheme", &track_core::make_mileage_time_scheme, "id"_a, "color"_a, "acceleration_profile"_a, "segments"_a); m.def("make_speed_time_scheme", &track_core::make_speed_time_scheme, "id"_a, "color"_a, "acceleration_profile"_a, "segments"_a); m.def("make_repeated_speed_mileage_time_scheme", &track_core::make_repeated_speed_mileage_time_scheme, "id"_a, "color"_a, "acceleration_profile"_a, "segments"_a); m.def("make_scheme_track_runtime", &make_scheme_track_runtime_checked, "scheme"_a); m.def("start_scheme_track", &track_core::start_scheme_track, "runtime"_a); m.def("stop_scheme_track", &track_core::stop_scheme_track, "runtime"_a); m.def("tick_scheme_track", &track_core::tick_scheme_track, "config"_a, "runtime"_a, "delta_s"_a); m.def("scheme_track_info", &track_core::scheme_track_info, "runtime"_a); m.def("scheme_track_report", &track_core::scheme_track_report, "runtime"_a); }