Files
track-core/src/python/bindings.cpp
T
crosstyan 1005e50be0 feat(track-core): add portable training runtimes
Move scheme and PID training runtime behavior into the pure track_core layer and expose render sinks for injected strip application.

Add ESP compatibility adapters, Python bindings/test scaffolding, and in-memory render support so app_track_bt can consume core render and runtime logic without duplicating it.

Cover circular/linear rendering boundaries, all scheme runtime types, scheme render_to parity, PID sample de-duplication, speed suppression, and live tuning in track-core tests.
2026-05-18 16:15:45 +08:00

311 lines
14 KiB
C++

#include <cstdint>
#include <stdexcept>
#include <string>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>
#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<const track_core::Color> 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_<track_core::TrackError>(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_<track_core::TrackDrawKind>(m, "TrackDrawKind")
.value("circular", track_core::TrackDrawKind::circular)
.value("linear", track_core::TrackDrawKind::linear);
nb::enum_<track_core::SchemeKind>(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_<track_core::AccelerationProfile>(m, "AccelerationProfile")
.value("instant", track_core::AccelerationProfile::instant)
.value("smooth", track_core::AccelerationProfile::smooth);
nb::enum_<track_core::TrackState>(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_<track_core::Color>(m, "Color")
.def(nb::init<>())
.def(nb::init<std::uint8_t, std::uint8_t, std::uint8_t>(), "r"_a, "g"_a, "b"_a)
.def(nb::init<std::uint32_t>(), "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<std::uint32_t>(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_<track_core::TrackConfig>(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_<track_core::TrackInfo>(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_<track_core::TrackSchemeMgrRead::Status>(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_<track_core::TrackReport>(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_<track_core::TrackRenderSpan>(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_<track_core::TrackRenderPlan>(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_<track_core::MemoryStrip>(m, "MemoryStrip")
.def(nb::init<std::size_t>(), "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_<track_core::SMSegment>(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_<track_core::MTSegment>(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_<track_core::STSegment>(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_<track_core::RepeatedSMSegment>(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_<track_core::DecodedScheme>(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_<track_core::SchemeTrackState>(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_<track_core::SchemeTrackRuntime>(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_<track_core::SchemeTrainingRuntime>(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);
}