Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd5c2f64e0 | |||
| 28327ed875 | |||
| 66fc8eb9a4 | |||
| 7a988778f4 |
+2
-12
@@ -9,21 +9,11 @@ set(TRACK_CORE_SOURCES
|
||||
)
|
||||
|
||||
if(DEFINED IDF_TARGET)
|
||||
set(TRACK_CORE_IDF_SOURCES
|
||||
src/esp/app_track_decoder.cpp
|
||||
src/esp/app_track_drawer.cpp
|
||||
src/esp/app_track_model.cpp
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${TRACK_CORE_SOURCES} ${TRACK_CORE_IDF_SOURCES}
|
||||
SRCS ${TRACK_CORE_SOURCES}
|
||||
INCLUDE_DIRS include
|
||||
REQUIRES
|
||||
app_proto
|
||||
app_strip_if
|
||||
app_utils
|
||||
app_utils_clock
|
||||
)
|
||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(track_core VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
@@ -5,6 +5,10 @@ Standalone C++ core for TrackBackFwd track simulation and strip rendering.
|
||||
This library is intentionally platform-neutral. It does not depend on ESP-IDF,
|
||||
FreeRTOS, protobuf/nanopb, BLE, or hardware LED drivers.
|
||||
|
||||
Firmware protobuf/ESP-IDF adapter types for the Track service live in
|
||||
`components/app_track_model` in the parent project. Keep this package focused on
|
||||
portable track state, scheme decoding/runtime, PID runtime, and render planning.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
@@ -14,7 +18,7 @@ ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
When used inside an ESP-IDF project under `components/track-core`, the same
|
||||
`CMakeLists.txt` registers an IDF component.
|
||||
`CMakeLists.txt` registers an IDF component for the portable core only.
|
||||
|
||||
## Python bindings
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#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
|
||||
@@ -1,40 +0,0 @@
|
||||
#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
@@ -50,8 +50,7 @@ enum class AccelerationProfile : std::uint8_t {
|
||||
enum class TrackState : std::uint8_t {
|
||||
stop = 0,
|
||||
run = 1,
|
||||
test_rainbow = 2,
|
||||
test_blink = 3,
|
||||
test_display = 4,
|
||||
};
|
||||
|
||||
enum class TrackControllerMode : std::uint8_t {
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "app_track_decoder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace app::track {
|
||||
namespace {
|
||||
|
||||
track_core::Color to_core(const Color &color) {
|
||||
return {
|
||||
color.inner.r,
|
||||
color.inner.g,
|
||||
color.inner.b,
|
||||
};
|
||||
}
|
||||
|
||||
error_t from_core(track_core::TrackError error) {
|
||||
switch (error) {
|
||||
case track_core::TrackError::ok:
|
||||
return ESP_OK;
|
||||
case track_core::TrackError::invalid_arg:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
case track_core::TrackError::invalid_size:
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
case track_core::TrackError::invalid_state:
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
case track_core::TrackError::not_supported:
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
case track_core::TrackError::range:
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TrackSchemeDecoder TrackSchemeDecoder::from_proto(const proto_type &proto) {
|
||||
TrackSchemeDecoder scheme;
|
||||
scheme.id = proto.id;
|
||||
if (proto.has_color) {
|
||||
scheme.color = Color::from_proto(proto.color);
|
||||
} else {
|
||||
scheme.color = Color::white();
|
||||
}
|
||||
auto data = std::span<const uint8_t>(proto.data.bytes, proto.data.size);
|
||||
std::ranges::copy(data, std::back_inserter(scheme.binary));
|
||||
return scheme;
|
||||
}
|
||||
|
||||
expected<track_core::DecodedScheme, error_t> TrackSchemeDecoder::decode_core() const {
|
||||
using ue = unexpected<error_t>;
|
||||
if (binary.empty()) {
|
||||
return ue{ESP_ERR_INVALID_ARG};
|
||||
}
|
||||
|
||||
const auto decoded = track_core::decode_scheme(id, to_core(color), binary);
|
||||
if (!decoded) {
|
||||
return ue{from_core(decoded.error())};
|
||||
}
|
||||
return *decoded;
|
||||
}
|
||||
|
||||
TrackSchemeMgr::Add TrackSchemeMgr::Add::from_proto(const proto_type &proto) {
|
||||
Add add;
|
||||
assert(proto.has_scheme);
|
||||
add.scheme_decoder = TrackSchemeDecoder::from_proto(proto.scheme);
|
||||
return add;
|
||||
}
|
||||
|
||||
TrackSchemeMgr TrackSchemeMgr::from_proto(const proto_type &proto) {
|
||||
TrackSchemeMgr mgmt;
|
||||
switch (proto.which_msg) {
|
||||
case track_app_TrackSchemeMgr_add_tag:
|
||||
mgmt.choice = Add::from_proto(proto.msg.add);
|
||||
break;
|
||||
case track_app_TrackSchemeMgr_clear_tag:
|
||||
mgmt.choice = Clear{};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return mgmt;
|
||||
}
|
||||
|
||||
} // namespace app::track
|
||||
@@ -1,179 +0,0 @@
|
||||
#include "app_track_drawer.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include "track_core/render.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
track_core::Color to_core(const app::track::Color &color) {
|
||||
return {
|
||||
color.inner.r,
|
||||
color.inner.g,
|
||||
color.inner.b,
|
||||
};
|
||||
}
|
||||
|
||||
app::track::Color from_core(const track_core::Color &color) {
|
||||
return {
|
||||
color.r,
|
||||
color.g,
|
||||
color.b,
|
||||
};
|
||||
}
|
||||
|
||||
track_core::TrackDrawKind to_core(track_app_TrackDrawKind draw_kind) {
|
||||
switch (draw_kind) {
|
||||
case track_app_TrackDrawKind_CIRCULAR:
|
||||
return track_core::TrackDrawKind::circular;
|
||||
case track_app_TrackDrawKind_LINEAR:
|
||||
return track_core::TrackDrawKind::linear;
|
||||
}
|
||||
return track_core::TrackDrawKind::circular;
|
||||
}
|
||||
|
||||
track_core::SchemeKind to_core(app::track::SchemeKind kind) {
|
||||
switch (kind) {
|
||||
case track_app_TrackSchemeKind_SPEED_INPUT_MILEAGE_SEGMENTED_TIME_FREE:
|
||||
return track_core::SchemeKind::speed_input_mileage_segmented_time_free;
|
||||
case track_app_TrackSchemeKind_MILEAGE_INPUT_TIME_SEGMENTED_SPEED_FREE:
|
||||
return track_core::SchemeKind::mileage_input_time_segmented_speed_free;
|
||||
case track_app_TrackSchemeKind_SPEED_INPUT_TIME_SEGMENTED_MILEAGE_FREE:
|
||||
return track_core::SchemeKind::speed_input_time_segmented_mileage_free;
|
||||
case track_app_TrackSchemeKind_REPEATED_SPEED_INPUT_MILEAGE_SEGMENTATION_INPUT_TIME_SEGMENTED:
|
||||
return track_core::SchemeKind::repeated_speed_input_mileage_segmentation_input_time_segmented;
|
||||
}
|
||||
return track_core::SchemeKind::speed_input_time_segmented_mileage_free;
|
||||
}
|
||||
|
||||
track_core::TrackState to_core(app::track::TrackState state) {
|
||||
switch (state) {
|
||||
case track_app_TrackState_STOP:
|
||||
return track_core::TrackState::stop;
|
||||
case track_app_TrackState_RUN:
|
||||
return track_core::TrackState::run;
|
||||
case track_app_TrackState_TEST_RAINBOW:
|
||||
return track_core::TrackState::test_rainbow;
|
||||
case track_app_TrackState_TEST_BLINK:
|
||||
return track_core::TrackState::test_blink;
|
||||
}
|
||||
return track_core::TrackState::stop;
|
||||
}
|
||||
|
||||
track_core::TrackConfig to_core(const app::track::TrackConfig &config) {
|
||||
return {
|
||||
.draw_kind = to_core(config.draw_kind),
|
||||
.line_length_m = config.line_length_m,
|
||||
.active_line_length_m = config.active_line_length_m,
|
||||
.head_offset_m = config.head_offset_m,
|
||||
.line_leds_num = config.line_leds_num,
|
||||
};
|
||||
}
|
||||
|
||||
track_core::TrackInfo to_core(const app::track::TrackInfo &info) {
|
||||
return {
|
||||
.kind = to_core(info.kind),
|
||||
.color = to_core(info.color),
|
||||
.id = info.id,
|
||||
.is_running = info.is_running,
|
||||
.num_segments = info.num_segments,
|
||||
};
|
||||
}
|
||||
|
||||
track_core::TrackReport to_core(const app::track::report &report) {
|
||||
return {
|
||||
.id = report.id,
|
||||
.state = to_core(report.state),
|
||||
.mileage_m = report.mileage_m,
|
||||
.speed_m_s = report.speed_m_s,
|
||||
.time_elapsed_ms = report.time_elapsed_ms,
|
||||
};
|
||||
}
|
||||
|
||||
track_core::TrackError to_core(error_t error) {
|
||||
if (error == ESP_OK) {
|
||||
return track_core::TrackError::ok;
|
||||
}
|
||||
return track_core::TrackError::invalid_state;
|
||||
}
|
||||
|
||||
app::strip::StripView *strip_from_context(void *context) {
|
||||
return static_cast<app::strip::StripView *>(context);
|
||||
}
|
||||
|
||||
track_core::TrackError strip_clear(void *context) {
|
||||
auto *strip = strip_from_context(context);
|
||||
if (strip == nullptr) {
|
||||
return track_core::TrackError::invalid_arg;
|
||||
}
|
||||
return to_core((*strip)->clear());
|
||||
}
|
||||
|
||||
track_core::TrackError strip_fill(
|
||||
void *context,
|
||||
std::uint16_t start_led,
|
||||
std::uint16_t led_count,
|
||||
track_core::Color color) {
|
||||
auto *strip = strip_from_context(context);
|
||||
if (strip == nullptr) {
|
||||
return track_core::TrackError::invalid_arg;
|
||||
}
|
||||
return to_core((*strip)->fill(start_led, led_count, static_cast<std::uint32_t>(from_core(color))));
|
||||
}
|
||||
|
||||
track_core::TrackError strip_show(void *context) {
|
||||
auto *strip = strip_from_context(context);
|
||||
if (strip == nullptr) {
|
||||
return track_core::TrackError::invalid_arg;
|
||||
}
|
||||
return to_core((*strip)->show());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace app::track {
|
||||
|
||||
void TrackRenderPlan::add_fill(uint16_t start_led, uint16_t led_count, Color color) {
|
||||
if (led_count == 0) {
|
||||
return;
|
||||
}
|
||||
assert(span_count < spans.size() && "TrackRenderPlan capacity exceeded");
|
||||
spans[span_count++] = TrackRenderSpan{
|
||||
.start_led = start_led,
|
||||
.led_count = led_count,
|
||||
.color = color,
|
||||
};
|
||||
}
|
||||
|
||||
TrackRenderPlan make_track_render_plan(const TrackConfig &config, const TrackInfo &info, const report &rep) {
|
||||
const auto core_plan = track_core::make_track_render_plan(
|
||||
to_core(config),
|
||||
to_core(info),
|
||||
to_core(rep));
|
||||
|
||||
TrackRenderPlan plan{};
|
||||
for (size_t i = 0; i < core_plan.span_count; ++i) {
|
||||
const auto &span = core_plan.spans[i];
|
||||
plan.add_fill(span.start_led, span.led_count, from_core(span.color));
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
void apply_render_plan(app::strip::StripView strip, const TrackRenderPlan &plan) {
|
||||
for (size_t i = 0; i < plan.span_count; ++i) {
|
||||
const auto &span = plan.spans[i];
|
||||
strip->fill(span.start_led, span.led_count, span.color);
|
||||
}
|
||||
}
|
||||
|
||||
track_core::TrackRenderSink make_track_render_sink(app::strip::StripView &strip) {
|
||||
return track_core::TrackRenderSink{
|
||||
.context = &strip,
|
||||
.clear = strip_clear,
|
||||
.fill = strip_fill,
|
||||
.show = strip_show,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace app::track
|
||||
@@ -1,109 +0,0 @@
|
||||
#include "app_track_model.hpp"
|
||||
#include "app_track.pb.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
namespace {
|
||||
app::track::config_getter global_config_getter{nullptr};
|
||||
constexpr auto TAG = "app::track";
|
||||
}
|
||||
|
||||
namespace app::track {
|
||||
const char *to_str(TrackState status) {
|
||||
using enum TrackState;
|
||||
switch (status) {
|
||||
case track_app_TrackState_STOP:
|
||||
return "STOP";
|
||||
case track_app_TrackState_RUN:
|
||||
return "RUN";
|
||||
case track_app_TrackState_TEST_RAINBOW:
|
||||
return "TEST_RAINBOW";
|
||||
case track_app_TrackState_TEST_BLINK:
|
||||
return "TEST_BLINK";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
const char *to_str(TrackControllerMode mode) {
|
||||
switch (mode) {
|
||||
case track_app_TrackControllerMode_SCHEME:
|
||||
return "SCHEME";
|
||||
case track_app_TrackControllerMode_PID_HR:
|
||||
return "PID_HR";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
const char *to_str(TrackPidStageKind kind) {
|
||||
switch (kind) {
|
||||
case track_app_TrackPidStageKind_NONE:
|
||||
return "NONE";
|
||||
case track_app_TrackPidStageKind_CONSTANT:
|
||||
return "CONSTANT";
|
||||
case track_app_TrackPidStageKind_PID:
|
||||
return "PID";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
const char *to_str(track_app_TrackDrawKind draw_kind) {
|
||||
switch (draw_kind) {
|
||||
case track_app_TrackDrawKind_CIRCULAR:
|
||||
return "CIRCULAR";
|
||||
case track_app_TrackDrawKind_LINEAR:
|
||||
return "LINEAR";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
const char *to_str(AccelerationProfile profile) {
|
||||
switch (profile) {
|
||||
case track_app_TrackAccelerationProfile_SMOOTH:
|
||||
return "SMOOTH";
|
||||
case track_app_TrackAccelerationProfile_INSTANT:
|
||||
return "INSTANT";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
void set_global_config_getter(config_getter getter) {
|
||||
global_config_getter = std::move(getter);
|
||||
}
|
||||
|
||||
const TrackConfig &global_config() {
|
||||
if (not global_config_getter) {
|
||||
static const auto DEFAULT_CONFIG = TrackConfig::Default();
|
||||
ESP_LOGW(TAG, "unset global config getter");
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
return global_config_getter();
|
||||
}
|
||||
|
||||
void TrackConfig::log(const char *tag, esp_log_level_t level) const {
|
||||
ESP_LOG_LEVEL(level, tag,
|
||||
"TrackConfig{.draw_kind=%s, "
|
||||
".line_length_m=%.2f, "
|
||||
".active_line_length_m=%.2f, "
|
||||
".head_offset_m=%.2f, "
|
||||
".line_leds_num=%" PRIu16
|
||||
"}",
|
||||
to_str(draw_kind),
|
||||
line_length_m,
|
||||
active_line_length_m,
|
||||
head_offset_m,
|
||||
line_leds_num);
|
||||
}
|
||||
|
||||
void TrackStateReportCollection::log(const char *tag, esp_log_level_t level) const {
|
||||
for (size_t i = 0; i < states.size(); ++i) {
|
||||
const auto &report = states[i];
|
||||
ESP_LOG_LEVEL(level, tag,
|
||||
"[%zu] Report{.id=%" PRIu8 ", .state=%s, .mileage_m=%.2f, .speed_m_s=%.2f, .time_elapsed_ms=%" PRIu32 "}",
|
||||
i,
|
||||
report.id,
|
||||
to_str(report.state),
|
||||
report.mileage_m,
|
||||
report.speed_m_s,
|
||||
report.time_elapsed_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,7 @@ NB_MODULE(_core, m) {
|
||||
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);
|
||||
.value("test_display", track_core::TrackState::test_display);
|
||||
|
||||
nb::class_<track_core::Color>(m, "Color")
|
||||
.def(nb::init<>())
|
||||
|
||||
Reference in New Issue
Block a user