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.
This commit is contained in:
2026-05-18 16:15:45 +08:00
parent 84598cad20
commit 1005e50be0
24 changed files with 4169 additions and 15 deletions
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <variant>
#include <vector>
#include "app_track_model.hpp"
#include "track_core/scheme_decoder.hpp"
namespace app::track {
struct TrackSchemeDecoder {
using proto_type = track_app_TrackScheme;
static inline const pb_msgdesc_t *pb_fields = &track_app_TrackScheme_msg;
TrackSchemeDecoder() = default;
[[nodiscard]]
static TrackSchemeDecoder from_proto(const proto_type &proto);
[[nodiscard]]
expected<track_core::DecodedScheme, error_t> decode_core() const;
uint8_t id = 0;
std::vector<uint8_t> binary;
Color color;
};
struct TrackSchemeMgr {
using proto_type = track_app_TrackSchemeMgr;
static inline const pb_msgdesc_t *pb_fields = &track_app_TrackSchemeMgr_msg;
struct Add {
using proto_type = track_app_TrackSchemeMgrAdd;
static inline const pb_msgdesc_t *pb_fields = &track_app_TrackSchemeMgrAdd_msg;
TrackSchemeDecoder scheme_decoder;
error_t err{ESP_OK};
static Add from_proto(const proto_type &proto);
};
struct Clear {};
using Unknown = std::monostate;
enum class MessageType : uint8_t {
NONE = 0,
ADD = 1,
CLEAR = 2
};
explicit TrackSchemeMgr() = default;
static TrackSchemeMgr from_proto(const proto_type &proto);
std::variant<Unknown, Add, Clear> choice{Unknown{}};
};
} // namespace app::track
+40
View File
@@ -0,0 +1,40 @@
#ifndef D2AF35D4_5EEF_406F_A3F6_F7E6F2AA24C4
#define D2AF35D4_5EEF_406F_A3F6_F7E6F2AA24C4
#include "app_strip_if.hpp"
#include "app_track_model.hpp"
#include "track_core/render.hpp"
#include <array>
#include <cstdint>
namespace app::track {
struct TrackRenderSpan {
uint16_t start_led{};
uint16_t led_count{};
Color color;
};
struct TrackRenderPlan {
static constexpr size_t MAX_SPANS = 4;
void add_fill(uint16_t start_led, uint16_t led_count, Color color);
[[nodiscard]]
bool empty() const {
return span_count == 0;
}
std::array<TrackRenderSpan, MAX_SPANS> spans{};
size_t span_count{};
};
[[nodiscard]]
TrackRenderPlan make_track_render_plan(const TrackConfig &config, const TrackInfo &info, const report &rep);
void apply_render_plan(app::strip::StripView strip, const TrackRenderPlan &plan);
[[nodiscard]]
track_core::TrackRenderSink make_track_render_sink(app::strip::StripView &strip);
}
#endif /* D2AF35D4_5EEF_406F_A3F6_F7E6F2AA24C4 */
File diff suppressed because it is too large Load Diff
+3
View File
@@ -46,4 +46,7 @@ private:
[[nodiscard]]
TrackError apply_render_plan(MemoryStrip &strip, const TrackRenderPlan &plan);
[[nodiscard]]
TrackRenderSink make_memory_strip_sink(MemoryStrip &strip);
} // namespace track_core
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include "track_core/model.hpp"
#include "track_core/pid_program.hpp"
namespace track_core {
struct TrackPidBandSnapshot {
std::uint8_t band_id{};
std::uint8_t heart_rate{};
std::uint32_t heart_rate_sample_seq{};
bool has_heart_rate{};
bool hr_is_fresh{};
std::uint16_t step_count{};
bool has_step_count{};
bool band_is_active{};
};
class PidHrRuntime {
public:
using time_point = clock::time_point;
PidHrRuntime();
explicit PidHrRuntime(TrackPidConfig config);
void set_pid_config(TrackPidConfig config);
void update_target_hr_bpm(std::uint8_t target_hr_bpm);
[[nodiscard]]
expected<unit, TrackError> apply_pid_runtime_command(
const TrackPidRuntimeCommand &command,
const TrackConfig *track_config);
[[nodiscard]]
const TrackPidConfig &pid_config() const;
void start(time_point now);
void stop();
void tick(const TrackConfig *track_config, const TrackPidBandSnapshot &band, time_point now);
[[nodiscard]]
TrackReport state_report(time_point now) const;
[[nodiscard]]
TrackInfo info() const;
[[nodiscard]]
TrackPidStatus pid_status(const TrackPidBandSnapshot &band, time_point now) const;
private:
static constexpr std::uint8_t magic_pid_track_id = 0;
[[nodiscard]]
float effective_speed_m_s(float base_speed_m_s, const TrackConfig *track_config) const;
TrackPidConfig config_{TrackPidConfig::default_config()};
bool running_{false};
Color color_{Color::white()};
time_point start_timestamp_{};
time_point last_tick_timestamp_{};
std::optional<std::uint32_t> last_consumed_hr_sample_seq_;
time_point last_consumed_hr_sample_time_{};
float mileage_m_{0.0F};
float base_speed_m_s_{0.0F};
float effective_speed_m_s_{0.0F};
std::unique_ptr<TrackPidProgramState> program_state_;
};
} // namespace track_core
+25
View File
@@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include "track_core/model.hpp"
@@ -27,10 +28,34 @@ struct TrackRenderPlan {
std::size_t span_count{};
};
struct TrackRenderSink {
using ClearFn = TrackError (*)(void *context);
using FillFn = TrackError (*)(
void *context,
std::uint16_t start_led,
std::uint16_t led_count,
Color color);
using ShowFn = TrackError (*)(void *context);
void *context{};
ClearFn clear{};
FillFn fill{};
ShowFn show{};
};
[[nodiscard]]
TrackRenderPlan make_track_render_plan(
const TrackConfig &config,
const TrackInfo &info,
const TrackReport &report);
[[nodiscard]]
TrackError clear_render_sink(TrackRenderSink sink);
[[nodiscard]]
TrackError apply_render_plan(TrackRenderSink sink, const TrackRenderPlan &plan);
[[nodiscard]]
TrackError show_render_sink(TrackRenderSink sink);
} // namespace track_core
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include <cstddef>
#include <vector>
#include "track_core/model.hpp"
#include "track_core/render.hpp"
#include "track_core/scheme_decoder.hpp"
namespace track_core {
struct SchemeTrackState {
bool is_running{};
std::size_t primary_segment_index{};
std::size_t sub_segment_index{};
float mileage_m{};
float loop_mileage_m{};
float speed_m_s{};
float elapsed_s{};
};
struct SchemeTrackRuntime {
DecodedScheme scheme;
SchemeTrackState state;
};
[[nodiscard]]
DecodedScheme make_speed_mileage_scheme(
std::uint8_t id,
Color color,
AccelerationProfile acceleration_profile,
std::vector<SMSegment> segments);
[[nodiscard]]
DecodedScheme make_mileage_time_scheme(
std::uint8_t id,
Color color,
AccelerationProfile acceleration_profile,
std::vector<MTSegment> segments);
[[nodiscard]]
DecodedScheme make_speed_time_scheme(
std::uint8_t id,
Color color,
AccelerationProfile acceleration_profile,
std::vector<STSegment> segments);
[[nodiscard]]
DecodedScheme make_repeated_speed_mileage_time_scheme(
std::uint8_t id,
Color color,
AccelerationProfile acceleration_profile,
std::vector<RepeatedSMSegment> segments);
[[nodiscard]]
expected<SchemeTrackRuntime, TrackError> make_scheme_track_runtime(DecodedScheme scheme);
[[nodiscard]]
SchemeTrackRuntime start_scheme_track(SchemeTrackRuntime runtime);
[[nodiscard]]
SchemeTrackRuntime stop_scheme_track(SchemeTrackRuntime runtime);
[[nodiscard]]
SchemeTrackRuntime tick_scheme_track(
const TrackConfig &config,
SchemeTrackRuntime runtime,
float delta_s);
[[nodiscard]]
TrackInfo scheme_track_info(const SchemeTrackRuntime &runtime);
[[nodiscard]]
TrackReport scheme_track_report(const SchemeTrackRuntime &runtime);
class SchemeTrainingRuntime {
public:
[[nodiscard]]
bool has_program() const noexcept;
[[nodiscard]]
bool all_stopped() const noexcept;
[[nodiscard]]
expected<unit, TrackError> add_scheme(DecodedScheme scheme);
void clear();
void start();
void stop();
void tick(const TrackConfig &config, float delta_s);
[[nodiscard]]
TrackStateReportCollection state_collection() const;
[[nodiscard]]
TrackSchemeMgrRead scheme_status() const;
[[nodiscard]]
expected<unit, TrackError> render_to(const TrackConfig &config, TrackRenderSink sink) const;
[[nodiscard]]
expected<std::vector<Color>, TrackError> render_pixels(const TrackConfig &config) const;
private:
std::vector<SchemeTrackRuntime> tracks_;
};
} // namespace track_core