diff --git a/CMakeLists.txt b/CMakeLists.txt index 56b44eb..1622730 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index 272495f..ab48e37 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/include/app_track_decoder.hpp b/include/app_track_decoder.hpp deleted file mode 100644 index 09894c1..0000000 --- a/include/app_track_decoder.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include - -#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]] - proto_type to_proto() const; - - [[nodiscard]] - expected decode_core() const; - - uint8_t id = 0; - std::vector 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); - [[nodiscard]] - proto_type to_proto() const; - }; - - 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); - [[nodiscard]] - proto_type to_proto() const; - - std::variant choice{Unknown{}}; -}; - -} // namespace app::track diff --git a/include/app_track_drawer.hpp b/include/app_track_drawer.hpp deleted file mode 100644 index d26dfa7..0000000 --- a/include/app_track_drawer.hpp +++ /dev/null @@ -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 -#include - -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 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 */ diff --git a/include/app_track_model.hpp b/include/app_track_model.hpp deleted file mode 100644 index d76aefc..0000000 --- a/include/app_track_model.hpp +++ /dev/null @@ -1,1186 +0,0 @@ -#ifndef E32F663C_9C5C_4317_882A_E6457E88B576 -#define E32F663C_9C5C_4317_882A_E6457E88B576 -/** - * @brief ESP-IDF/protobuf compatibility model for the Track BLE service - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "app_track.pb.h" -#include "app_const.hpp" -#include "app_color.hpp" -#include "app_utils.hpp" -#include "esp_err.h" -#include "app_clock.hpp" -#include "esp_log_level.h" - -#ifndef APP_TRACK_C_ARRAY_SIZE -/** - * @brief A macro to calculate the size of a C-style array - * @param arr The array to calculate the size of - */ -#define APP_TRACK_C_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -#endif - -#ifndef __packed -#if defined(__GNUC__) || defined(__clang__) -#define __packed __attribute__((__packed__)) -#elif defined(_MSC_VER) -#define __packed __declspec(align(1)) -#else -#error "Unsupported compiler for __packed attribute" -#endif // __GNUC__ || __clang__ -#endif // __packed - -namespace app::track { -template -using expected = std::expected; - -template -using unexpected = std::unexpected; - -template -using optional = std::optional; - -using unit = std::monostate; - -using error_t = esp_err_t; - -using SchemeKind = track_app_TrackSchemeKind; -using AccelerationProfile = track_app_TrackAccelerationProfile; - - -struct Color { - using proto_type = track_app_Color; - static inline const pb_msgdesc_t *pb_fields = &track_app_Color_msg; - - - union { - struct { - uint8_t r; - uint8_t g; - uint8_t b; - }; - uint8_t bytes[3]; - } inner{}; - - Color() = default; - constexpr Color(uint8_t r, uint8_t g, uint8_t b) : inner{.r = r, .g = g, .b = b} {} - constexpr Color(uint32_t value) { - inner.r = (value >> 16) & 0xFF; - inner.g = (value >> 8) & 0xFF; - inner.b = value & 0xFF; - } - constexpr Color(std::span bytes) { - inner.r = bytes[0]; - inner.g = bytes[1]; - inner.b = bytes[2]; - } - - constexpr bool operator==(const Color &other) const { - return inner.r == other.inner.r && - inner.g == other.inner.g && - inner.b == other.inner.b; - } - - constexpr bool operator!=(const Color &other) const { - return not(*this == other); - } - - operator uint32_t() const { - return ((uint32_t)inner.r << 16) | ((uint32_t)inner.g << 8) | inner.b; - } - - explicit operator std::string() const { - return std::format("#{:02X}{:02X}{:02X}", - static_cast(inner.r), - static_cast(inner.g), - static_cast(inner.b)); - } - - [[nodiscard]] - std::string string() const { - return static_cast(*this); - } - - static Color from_proto(const proto_type &proto) { - return {proto.r, proto.g, proto.b}; - } - - static Color black() { - return {0, 0, 0}; - } - - static Color green() { - return {0, 255, 0}; - } - - static Color red() { - return {255, 0, 0}; - } - - static Color blue() { - return {0, 0, 255}; - } - - static Color white() { - return {255, 255, 255}; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto{}; - proto.r = inner.r; - proto.g = inner.g; - proto.b = inner.b; - return proto; - } - - [[nodiscard]] - app::utils::Color to_app_color() const { - return app::utils::Color{inner.r, inner.g, inner.b}; - } -}; - -struct TrackInfo { - SchemeKind kind{track_app_TrackSchemeKind_SPEED_INPUT_TIME_SEGMENTED_MILEAGE_FREE}; - Color color{Color::white()}; - uint8_t id{}; - bool is_running{}; - uint8_t num_segments{}; -}; - -struct TrackConfig { - using proto_type = track_app_TrackConfig; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackConfig_msg; - using TrackDrawKind = track_app_TrackDrawKind; - - /** properties */ - TrackDrawKind draw_kind{track_app_TrackDrawKind_CIRCULAR}; - float line_length_m{0.0F}; - float active_line_length_m{0.0F}; - float head_offset_m{0.0F}; - uint16_t line_leds_num{0}; - - [[nodiscard]] - float led_distance() const { - return line_leds_num > 0 ? line_length_m / static_cast(line_leds_num) : 0.0F; - } - - static TrackConfig from_proto(const proto_type &proto) { - TrackConfig config; - config.draw_kind = static_cast(proto.draw_kind); - config.line_length_m = proto.line_length_m; - config.active_line_length_m = proto.active_line_length_m; - config.head_offset_m = proto.head_offset_m; - config.line_leds_num = proto.line_leds_num; - return config; - } - - /** - * @brief a reasonable default configuration - */ - static TrackConfig Default() { - return {track_app_TrackDrawKind_CIRCULAR, - 400.0F, - 10.0F, - 0.0F, - 400}; - } - - [[nodiscard]] - bool verify() const { - bool length_non_zero_ok = line_length_m > 0.0F && active_line_length_m > 0.0F; - if (not length_non_zero_ok) { - return false; - } - // active line length cannot exceed total line length - if (active_line_length_m > line_length_m) { - return false; - } - // head offset must be within the line length - if (head_offset_m >= line_length_m) { - return false; - } - return true; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto{}; - proto.draw_kind = static_cast(draw_kind); - proto.line_length_m = line_length_m; - proto.active_line_length_m = active_line_length_m; - proto.head_offset_m = head_offset_m; - proto.line_leds_num = line_leds_num; - return proto; - } - - void log(const char *tag, esp_log_level_t level) const; -}; - - -struct TrackSchemeMgrRead { - using proto_type = track_app_TrackSchemeMgrRead; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackSchemeMgrRead_msg; - static constexpr size_t MAX_SCHEMES = APP_TRACK_C_ARRAY_SIZE( - track_app_TrackSchemeMgrRead::scheme_status); - - struct Status { - using proto_type = track_app_TrackSchemeMgrStatus; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackSchemeMgrStatus_msg; - - uint8_t id = 0; - uint8_t segment_count = 0; - - static Status from_proto(const proto_type &proto) { - return {proto.id, proto.segment_count}; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto{}; - proto.id = id; - proto.segment_count = segment_count; - return proto; - } - }; - - static TrackSchemeMgrRead from_proto(const proto_type &proto) { - TrackSchemeMgrRead collection; - collection.scheme_status.reserve(proto.scheme_status_count); - for (pb_size_t i = 0; i < proto.scheme_status_count; ++i) { - collection.scheme_status.push_back(Status::from_proto(proto.scheme_status[i])); - } - return collection; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackSchemeMgrRead_init_default; - proto.scheme_status_count = std::min(scheme_status.size(), MAX_SCHEMES); - for (size_t i = 0; i < proto.scheme_status_count; ++i) { - proto.scheme_status[i] = scheme_status[i].to_proto(); - } - return proto; - } - - /** properties */ - std::vector scheme_status; -}; - -using TrackState = track_app_TrackState; -using TrackControllerMode = track_app_TrackControllerMode; -using TrackDisplayTestPattern = track_app_TrackDisplayTestPattern; -using TrackDisplayTestEffect = track_app_TrackDisplayTestEffect; -using TrackPidStageKind = track_app_TrackPidStageKind; - -constexpr float PID_COMPAT_OUTPUT_SCALE_MAX_M_S = 10.0F; - -struct TrackPidFineTune { - using proto_type = track_app_TrackPidFineTune; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidFineTune_msg; - - uint8_t band_plus{0}; - uint8_t band_minus{0}; - float gain_scale{0.0F}; - - static TrackPidFineTune from_proto(const proto_type &proto) { - return { - static_cast(proto.band_plus), - static_cast(proto.band_minus), - proto.gain_scale, - }; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidFineTune_init_default; - proto.band_plus = band_plus; - proto.band_minus = band_minus; - proto.gain_scale = gain_scale; - return proto; - } -}; - -struct TrackPidSpeedSuppression { - using proto_type = track_app_TrackPidSpeedSuppression; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidSpeedSuppression_msg; - - float ratio_min{0.1F}; - float sigma_m{1.0F}; - - static TrackPidSpeedSuppression from_proto(const proto_type &proto) { - return { - .ratio_min = proto.ratio_min, - .sigma_m = proto.sigma_m, - }; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidSpeedSuppression_init_default; - proto.ratio_min = ratio_min; - proto.sigma_m = sigma_m; - return proto; - } -}; - -struct TrackPidSegment { - using proto_type = track_app_TrackPidSegment; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidSegment_msg; - - uint16_t duration_s{0}; - float min_speed_m_s{0.0F}; - float max_speed_m_s{0.0F}; - float kp{0.0F}; - float ki{1.0F}; - float kd{0.0F}; - float slew_rate_limit{0.0F}; - optional fine_tune; - - static TrackPidSegment Default() { - return { - .duration_s = 300, - .min_speed_m_s = 0.0F, - .max_speed_m_s = 4.0F, - .kp = app::global::constants::DEFAULT_PID_Kp, - .ki = app::global::constants::DEFAULT_PID_Ki, - .kd = app::global::constants::DEFAULT_PID_Kd, - .slew_rate_limit = app::global::constants::DEFAULT_PID_SLEW_RATE_LIMIT_M_S, - .fine_tune = std::nullopt, - }; - } - - static TrackPidSegment from_proto(const proto_type &proto) { - TrackPidSegment segment; - segment.duration_s = static_cast(proto.duration_s); - segment.min_speed_m_s = proto.min_speed_m_s; - segment.max_speed_m_s = proto.max_speed_m_s; - segment.kp = proto.kp; - segment.ki = proto.ki; - segment.kd = proto.kd; - segment.slew_rate_limit = proto.slew_rate_limit; - if (proto.has_fine_tune) { - segment.fine_tune = TrackPidFineTune::from_proto(proto.fine_tune); - } - return segment; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidSegment_init_default; - proto.duration_s = duration_s; - proto.min_speed_m_s = min_speed_m_s; - proto.max_speed_m_s = max_speed_m_s; - proto.kp = kp; - proto.ki = ki; - proto.kd = kd; - proto.slew_rate_limit = slew_rate_limit; - if (fine_tune) { - proto.has_fine_tune = true; - proto.fine_tune = fine_tune->to_proto(); - } - return proto; - } -}; - -struct TrackConstantSegment { - using proto_type = track_app_TrackConstantSegment; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackConstantSegment_msg; - - uint16_t duration_s{0}; - float speed_m_s{0.0F}; - - static TrackConstantSegment from_proto(const proto_type &proto) { - return { - .duration_s = static_cast(proto.duration_s), - .speed_m_s = proto.speed_m_s, - }; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackConstantSegment_init_default; - proto.duration_s = duration_s; - proto.speed_m_s = speed_m_s; - return proto; - } -}; - -struct TrackPidSchema { - using proto_type = track_app_TrackPidSchema; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidSchema_msg; - using variant_type = std::variant; - - variant_type segment{TrackConstantSegment{}}; - - static expected try_from_proto(const proto_type &proto) { - switch (proto.which_segment) { - case track_app_TrackPidSchema_pid_tag: - return TrackPidSchema{TrackPidSegment::from_proto(proto.segment.pid)}; - case track_app_TrackPidSchema_constant_tag: - return TrackPidSchema{TrackConstantSegment::from_proto(proto.segment.constant)}; - default: - return unexpected{ESP_ERR_INVALID_ARG}; - } - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidSchema_init_default; - std::visit(app::utils::overloads{ - [&](const TrackPidSegment &pid) { - proto.which_segment = track_app_TrackPidSchema_pid_tag; - proto.segment.pid = pid.to_proto(); - }, - [&](const TrackConstantSegment &constant) { - proto.which_segment = track_app_TrackPidSchema_constant_tag; - proto.segment.constant = constant.to_proto(); - }}, - segment); - return proto; - } - - [[nodiscard]] - TrackPidStageKind kind() const { - return std::holds_alternative(segment) - ? track_app_TrackPidStageKind_PID - : track_app_TrackPidStageKind_CONSTANT; - } -}; - -struct TrackPidConfig { - using proto_type = track_app_TrackPidConfig; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidConfig_msg; - - uint8_t band_id{0}; - uint8_t target_hr_bpm{120}; - uint8_t deadzone_bpm{3}; - bool preemptive_pid_activation{false}; - optional speed_suppression; - std::vector schemas{}; - - static TrackPidConfig Default() { - TrackPidConfig config; - config.schemas.push_back(TrackPidSchema{TrackPidSegment::Default()}); - return config; - } - - static TrackPidConfig from_proto(const proto_type &proto) { - auto config = try_from_proto(proto); - if (config) { - return *config; - } - return Default(); - } - - [[nodiscard]] - bool empty() const { - return schemas.empty(); - } - - [[nodiscard]] - static expected try_from_proto(const proto_type &proto) { - return try_from_proto_impl(proto, false); - } - - [[nodiscard]] - static expected try_from_proto_allow_empty(const proto_type &proto) { - return try_from_proto_impl(proto, true); - } - -private: - [[nodiscard]] - static expected try_from_proto_impl(const proto_type &proto, bool allow_empty) { - TrackPidConfig config; - config.band_id = static_cast(proto.band_id); - config.target_hr_bpm = static_cast(proto.target_hr_bpm); - config.deadzone_bpm = static_cast(proto.deadzone_bpm); - config.preemptive_pid_activation = proto.preemptive_pid_activation; - if (proto.has_speed_suppression) { - config.speed_suppression = TrackPidSpeedSuppression::from_proto(proto.speed_suppression); - } - config.schemas.reserve(proto.schemas_count); - for (pb_size_t i = 0; i < proto.schemas_count; ++i) { - auto schema = TrackPidSchema::try_from_proto(proto.schemas[i]); - if (!schema) { - return unexpected{schema.error()}; - } - config.schemas.push_back(*schema); - } - auto validation = config.validate(allow_empty); - if (!validation) { - return unexpected{validation.error()}; - } - return {std::move(config)}; - } - -public: - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidConfig_init_default; - proto.band_id = band_id; - proto.target_hr_bpm = target_hr_bpm; - proto.deadzone_bpm = deadzone_bpm; - proto.preemptive_pid_activation = preemptive_pid_activation; - if (speed_suppression) { - proto.has_speed_suppression = true; - proto.speed_suppression = speed_suppression->to_proto(); - } - proto.schemas_count = std::min(schemas.size(), APP_TRACK_C_ARRAY_SIZE(proto.schemas)); - for (size_t i = 0; i < proto.schemas_count; ++i) { - proto.schemas[i] = schemas[i].to_proto(); - } - return proto; - } - - [[nodiscard]] - expected validate(bool allow_empty = false) const { - const auto speed_suppression_ok = !speed_suppression.has_value() || - (std::isfinite(speed_suppression->ratio_min) && - std::isfinite(speed_suppression->sigma_m) && - speed_suppression->ratio_min > 0.0F && - speed_suppression->ratio_min < 1.0F && - speed_suppression->sigma_m > 0.0F); - if (!speed_suppression_ok) { - return unexpected{ESP_ERR_INVALID_ARG}; - } - if (schemas.empty()) { - if (allow_empty) { - return {}; - } - return unexpected{ESP_ERR_INVALID_ARG}; - } - for (const auto &schema : schemas) { - auto ok = std::visit(app::utils::overloads{ - [](const TrackPidSegment &pid) { - return pid.duration_s > 0 && - std::isfinite(pid.min_speed_m_s) && - std::isfinite(pid.max_speed_m_s) && - std::isfinite(pid.kp) && - std::isfinite(pid.ki) && - std::isfinite(pid.kd) && - std::isfinite(pid.slew_rate_limit) && - (!pid.fine_tune.has_value() || - (std::isfinite(pid.fine_tune->gain_scale) && - pid.fine_tune->gain_scale >= 0.0F)) && - pid.min_speed_m_s >= 0.0F && - pid.max_speed_m_s >= 0.0F && - pid.min_speed_m_s <= pid.max_speed_m_s && - pid.kp >= 0.0F && - pid.ki >= 0.0F && - pid.kd >= 0.0F && - pid.slew_rate_limit >= 0.0F; - }, - [](const TrackConstantSegment &constant) { - return constant.duration_s > 0 && - std::isfinite(constant.speed_m_s) && - constant.speed_m_s >= 0.0F; - }}, - schema.segment); - if (!ok) { - return unexpected{ESP_ERR_INVALID_ARG}; - } - } - return {}; - } -}; - -struct TrackPidSetTargetHr { - using proto_type = track_app_TrackPidSetTargetHr; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidSetTargetHr_msg; - - uint8_t target_hr_bpm{120}; - - static TrackPidSetTargetHr from_proto(const proto_type &proto) { - return { - .target_hr_bpm = static_cast(proto.target_hr_bpm), - }; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidSetTargetHr_init_default; - proto.target_hr_bpm = target_hr_bpm; - return proto; - } -}; - -struct TrackPidSetTuning { - using proto_type = track_app_TrackPidSetTuning; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidSetTuning_msg; - - float min_speed_m_s{0.0F}; - float max_speed_m_s{0.0F}; - float kp{0.0F}; - float ki{1.0F}; - float kd{0.0F}; - float slew_rate_limit{0.0F}; - optional fine_tune; - - static TrackPidSetTuning from_proto(const proto_type &proto) { - TrackPidSetTuning tuning; - tuning.min_speed_m_s = proto.min_speed_m_s; - tuning.max_speed_m_s = proto.max_speed_m_s; - tuning.kp = proto.kp; - tuning.ki = proto.ki; - tuning.kd = proto.kd; - tuning.slew_rate_limit = proto.slew_rate_limit; - if (proto.has_fine_tune) { - tuning.fine_tune = TrackPidFineTune::from_proto(proto.fine_tune); - } - return tuning; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidSetTuning_init_default; - proto.min_speed_m_s = min_speed_m_s; - proto.max_speed_m_s = max_speed_m_s; - proto.kp = kp; - proto.ki = ki; - proto.kd = kd; - proto.slew_rate_limit = slew_rate_limit; - if (fine_tune) { - proto.has_fine_tune = true; - proto.fine_tune = fine_tune->to_proto(); - } - return proto; - } - - [[nodiscard]] - expected validate() const { - const auto fine_tune_ok = !fine_tune.has_value() || - (std::isfinite(fine_tune->gain_scale) && fine_tune->gain_scale >= 0.0F); - if (!std::isfinite(min_speed_m_s) || - !std::isfinite(max_speed_m_s) || - !std::isfinite(kp) || - !std::isfinite(ki) || - !std::isfinite(kd) || - !std::isfinite(slew_rate_limit) || - !fine_tune_ok || - min_speed_m_s < 0.0F || - max_speed_m_s < 0.0F || - min_speed_m_s > max_speed_m_s || - kp < 0.0F || - ki < 0.0F || - kd < 0.0F || - slew_rate_limit < 0.0F) { - return unexpected{ESP_ERR_INVALID_ARG}; - } - return {}; - } - - void apply_to(TrackPidSegment &segment) const { - segment.min_speed_m_s = min_speed_m_s; - segment.max_speed_m_s = max_speed_m_s; - segment.kp = kp; - segment.ki = ki; - segment.kd = kd; - segment.slew_rate_limit = slew_rate_limit; - segment.fine_tune = fine_tune; - } -}; - -struct TrackPidRuntimeCommand { - using proto_type = track_app_TrackPidRuntimeCommand; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidRuntimeCommand_msg; - - std::variant command{unit{}}; - - static TrackPidRuntimeCommand from_proto(const proto_type &proto) { - switch (proto.which_command) { - case track_app_TrackPidRuntimeCommand_set_target_hr_tag: - return TrackPidRuntimeCommand{ - .command = TrackPidSetTargetHr::from_proto(proto.command.set_target_hr), - }; - case track_app_TrackPidRuntimeCommand_set_tuning_tag: - return TrackPidRuntimeCommand{ - .command = TrackPidSetTuning::from_proto(proto.command.set_tuning), - }; - default: - return TrackPidRuntimeCommand{}; - } - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidRuntimeCommand_init_default; - std::visit(app::utils::overloads{ - [](unit) { - // do nothing - }, - [&](const TrackPidSetTargetHr &target_hr) { - proto.which_command = track_app_TrackPidRuntimeCommand_set_target_hr_tag; - proto.command.set_target_hr = target_hr.to_proto(); - }, - [&](const TrackPidSetTuning &tuning) { - proto.which_command = track_app_TrackPidRuntimeCommand_set_tuning_tag; - proto.command.set_tuning = tuning.to_proto(); - }}, - command); - return proto; - } -}; - -struct TrackPidStatus { - using proto_type = track_app_TrackPidStatus; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackPidStatus_msg; - - uint8_t band_id{0}; - bool band_is_active{false}; - bool is_heart_rate_valid{false}; - uint8_t heart_rate_bpm{0}; - uint16_t step_count{0}; - uint8_t active_segment_index{0}; - TrackPidStageKind active_segment_kind{track_app_TrackPidStageKind_NONE}; - float effective_speed_m_s{0.0F}; - uint32_t remaining_program_ms{0}; - float base_speed_m_s{0.0F}; - - static TrackPidStatus Default() { - return { - .band_id = 0, - .band_is_active = false, - .is_heart_rate_valid = false, - .heart_rate_bpm = 0, - .step_count = 0, - .active_segment_index = 0, - .active_segment_kind = track_app_TrackPidStageKind_NONE, - .effective_speed_m_s = 0.0F, - .remaining_program_ms = 0, - .base_speed_m_s = 0.0F, - }; - } - - static TrackPidStatus from_proto(const proto_type &proto) { - return { - .band_id = static_cast(proto.band_id), - .band_is_active = proto.band_is_active, - .is_heart_rate_valid = proto.is_heart_rate_valid, - .heart_rate_bpm = static_cast(proto.heart_rate_bpm), - .step_count = static_cast(proto.step_count), - .active_segment_index = static_cast(proto.active_segment_index), - .active_segment_kind = proto.active_segment_kind, - .effective_speed_m_s = proto.effective_speed_m_s, - .remaining_program_ms = proto.remaining_program_ms, - .base_speed_m_s = proto.base_speed_m_s, - }; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackPidStatus_init_default; - proto.band_id = band_id; - proto.band_is_active = band_is_active; - proto.is_heart_rate_valid = is_heart_rate_valid; - proto.heart_rate_bpm = heart_rate_bpm; - proto.step_count = step_count; - proto.active_segment_index = active_segment_index; - proto.active_segment_kind = active_segment_kind; - proto.effective_speed_m_s = effective_speed_m_s; - proto.remaining_program_ms = remaining_program_ms; - proto.base_speed_m_s = base_speed_m_s; - return proto; - } -}; - -struct TrackDisplayTestParameters { - using proto_type = track_app_TrackDisplayTestParameters; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackDisplayTestParameters_msg; - - TrackDisplayTestPattern pattern{ - track_app_TrackDisplayTestPattern_TRACK_DISPLAY_TEST_PATTERN_BLINK}; - TrackDisplayTestEffect effect{ - track_app_TrackDisplayTestEffect_TRACK_DISPLAY_TEST_EFFECT_SOLID}; - uint8_t palette_index{15}; - optional color{std::nullopt}; - uint16_t period_ms{500}; - bool has_target_range{false}; - uint16_t target_start{0}; - uint16_t target_end{0}; - bool wrap{false}; - uint16_t window_size{1}; - uint16_t step{1}; - bool preserve_others{false}; - uint8_t blink_duty_cycle_percent{128}; - uint16_t repeat_count{3}; - optional random_seed{std::nullopt}; - - static TrackDisplayTestParameters from_proto(const proto_type &proto) { - TrackDisplayTestParameters params; - params.pattern = proto.pattern; - params.effect = proto.effect; - params.palette_index = proto.palette_index; - if (proto.has_color) { - params.color = Color::from_proto(proto.color); - } - params.period_ms = proto.period_ms; - params.has_target_range = proto.has_target_range; - params.target_start = proto.target_start; - params.target_end = proto.target_end; - params.wrap = proto.wrap; - params.window_size = proto.window_size; - params.step = proto.step; - params.preserve_others = proto.preserve_others; - params.blink_duty_cycle_percent = proto.blink_duty_cycle_percent; - params.repeat_count = proto.repeat_count; - if (proto.has_random_seed) { - params.random_seed = proto.random_seed; - } - return params; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto{}; - proto.pattern = pattern; - proto.effect = effect; - proto.palette_index = palette_index; - if (color.has_value()) { - proto.has_color = true; - proto.color = color->to_proto(); - } - proto.period_ms = period_ms; - proto.has_target_range = has_target_range; - proto.target_start = target_start; - proto.target_end = target_end; - proto.wrap = wrap; - proto.window_size = window_size; - proto.step = step; - proto.preserve_others = preserve_others; - proto.blink_duty_cycle_percent = blink_duty_cycle_percent; - proto.repeat_count = repeat_count; - if (random_seed.has_value()) { - proto.has_random_seed = true; - proto.random_seed = *random_seed; - } - return proto; - } - - static const char *pattern_to_str(TrackDisplayTestPattern value) { - switch (value) { - case track_app_TrackDisplayTestPattern_TRACK_DISPLAY_TEST_PATTERN_LIGHT: - return "LIGHT"; - case track_app_TrackDisplayTestPattern_TRACK_DISPLAY_TEST_PATTERN_BLINK: - return "BLINK"; - case track_app_TrackDisplayTestPattern_TRACK_DISPLAY_TEST_PATTERN_SCAN: - return "SCAN"; - case track_app_TrackDisplayTestPattern_TRACK_DISPLAY_TEST_PATTERN_CONTINUOUS_RAINBOW: - return "CONTINUOUS_RAINBOW"; - } - return "UNKNOWN"; - } - - static const char *effect_to_str(TrackDisplayTestEffect value) { - switch (value) { - case track_app_TrackDisplayTestEffect_TRACK_DISPLAY_TEST_EFFECT_SOLID: - return "SOLID"; - case track_app_TrackDisplayTestEffect_TRACK_DISPLAY_TEST_EFFECT_RAINBOW: - return "RAINBOW"; - case track_app_TrackDisplayTestEffect_TRACK_DISPLAY_TEST_EFFECT_RANDOM: - return "RANDOM"; - } - return "UNKNOWN"; - } - - void log(const char *tag, esp_log_level_t level) const { - const auto color_text = color.has_value() ? color->string() : std::string{""}; - ESP_LOG_LEVEL(level, tag, - "TrackDisplayTestParameters{" - ".pattern=%s, " - ".effect=%s, " - ".palette_index=%" PRIu8 ", " - ".color=%s, " - ".period_ms=%" PRIu16 ", " - ".has_target_range=%d, " - ".target_start=%" PRIu16 ", " - ".target_end=%" PRIu16 ", " - ".wrap=%d, " - ".window_size=%" PRIu16 ", " - ".step=%" PRIu16 ", " - ".preserve_others=%d, " - ".blink_duty_cycle_percent=%" PRIu8 ", " - ".repeat_count=%" PRIu16 ", " - ".has_random_seed=%d, " - ".random_seed=%" PRIu32 "}", - pattern_to_str(pattern), - effect_to_str(effect), - palette_index, - color_text.c_str(), - period_ms, - has_target_range, - target_start, - target_end, - wrap, - window_size, - step, - preserve_others, - blink_duty_cycle_percent, - repeat_count, - random_seed.has_value(), - random_seed.value_or(0)); - } -}; - -struct TrackControlMsg { - using proto_type = track_app_TrackControlMsg; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackControlMsg_msg; - - /** properties */ - std::variant state{unit{}}; - - static TrackControlMsg from_proto(const proto_type &proto) { - switch (proto.which_msg) { - case track_app_TrackControlMsg_set_state_tag: - return TrackControlMsg{.state = static_cast(proto.msg.set_state)}; - case track_app_TrackControlMsg_set_mode_tag: - return TrackControlMsg{.state = static_cast(proto.msg.set_mode)}; - case track_app_TrackControlMsg_set_display_test_parameters_tag: - return TrackControlMsg{.state = TrackDisplayTestParameters::from_proto(proto.msg.set_display_test_parameters)}; - default: - return TrackControlMsg{.state = unit{}}; - } - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackControlMsg_init_default; - std::visit(app::utils::overloads{ - [](unit) { - // do nothing - }, - [&](TrackState track_state) { - proto.which_msg = track_app_TrackControlMsg_set_state_tag; - proto.msg.set_state = static_cast(track_state); - }, - [&](const TrackDisplayTestParameters &test_params) { - proto.which_msg = track_app_TrackControlMsg_set_display_test_parameters_tag; - proto.msg.set_display_test_parameters = test_params.to_proto(); - }, - [&](TrackControllerMode mode) { - proto.which_msg = track_app_TrackControlMsg_set_mode_tag; - proto.msg.set_mode = static_cast(mode); - }}, - state); - return proto; - } -}; - - -struct TrackStateReportCollection { - using proto_type = track_app_TrackStateReportCollection; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackStateReportCollection_msg; - constexpr static size_t MAX_STATES = APP_TRACK_C_ARRAY_SIZE(proto_type::states); - - struct Report { - using proto_type = track_app_TrackStateReport; - static inline const pb_msgdesc_t *pb_fields = &track_app_TrackStateReport_msg; - - uint8_t id{0}; - TrackState state{TrackState::track_app_TrackState_STOP}; - float mileage_m{0.0F}; - float speed_m_s{0.0F}; - uint32_t time_elapsed_ms{0}; - - static Report from_proto(const proto_type &proto) { - return {proto.id, - static_cast(proto.state), - proto.mileage_m, - proto.speed_m_s, - proto.time_elapsed_ms}; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto{}; - proto.id = id; - proto.state = static_cast(state); - proto.mileage_m = mileage_m; - proto.speed_m_s = speed_m_s; - proto.time_elapsed_ms = time_elapsed_ms; - return proto; - } - }; - - /** properties */ - std::vector states; - - static TrackStateReportCollection from_proto(const proto_type &proto) { - TrackStateReportCollection collection; - collection.states.reserve(proto.states_count); - for (pb_size_t i = 0; i < proto.states_count; ++i) { - collection.states.push_back(Report::from_proto(proto.states[i])); - } - return collection; - } - - [[nodiscard]] - proto_type to_proto() const { - proto_type proto = track_app_TrackStateReportCollection_init_default; - proto.states_count = std::min(states.size(), MAX_STATES); - for (size_t i = 0; i < proto.states_count; ++i) { - proto.states[i] = states[i].to_proto(); - } - return proto; - } - - void log(const char *tag, esp_log_level_t level) const; -}; - -/** - * @brief Speed-Mileage training segment - */ -struct SMSegment { - struct __attribute__((packed)) raw { - static constexpr float SPEED_LSB = 10 / 255.0F; - /** - * @brief Speed in m/s - * @note LSB: ((10m/s) / 255) = 0.039m/s - */ - uint8_t speed_m_s; - uint16_t mileage_from_start_m; - }; - static constexpr size_t RAW_SIZE = sizeof(raw); - - static SMSegment from_raw(const raw &segment) { - return {.speed_m_s = static_cast(segment.speed_m_s) * raw::SPEED_LSB, - .mileage_from_start_m = segment.mileage_from_start_m}; - } - - [[nodiscard]] - raw to_raw() const { - raw segment{ - .speed_m_s = static_cast(std::round(speed_m_s / raw::SPEED_LSB)), - .mileage_from_start_m = mileage_from_start_m}; - return segment; - } - - - /** properties */ - - /** - * @brief speed in m/s - */ - float speed_m_s; - /** - * @brief absolute start mileage since the training (not difference nor the mileage the segment should maintain) - */ - uint16_t mileage_from_start_m; -}; - -/** - * @brief Time-Mileage training segment - */ -struct MTSegment { - /** - * @brief not the absolute mileage from start, but the mileage to travel in this segment - */ - uint16_t mileage_to_travel_this_segment_m; - /** - * @brief absolute time since the start of training (not time difference since last segment) - */ - uint16_t time_since_start_s; -}; - -/** - * @brief Speed-Time training segment - */ -struct STSegment { - struct __attribute__((packed)) raw { - static constexpr float SPEED_LSB = 10 / 255.0F; - /** - * @brief Speed in m/s - * @note LSB: ((10m/s) / 255) = 0.039m/s - */ - uint8_t speed_m_s; - uint16_t time_since_start_s; - }; - static constexpr size_t RAW_SIZE = sizeof(raw); - - static STSegment from_raw(const raw &segment) { - return {.speed_m_s = static_cast(segment.speed_m_s) * raw::SPEED_LSB, - .time_since_start_s = segment.time_since_start_s}; - } - - [[nodiscard]] - raw to_raw() const { - raw segment{ - .speed_m_s = static_cast(std::round(speed_m_s / raw::SPEED_LSB)), - .time_since_start_s = time_since_start_s}; - return segment; - } - - - /** properties */ - - /** - * @brief speed to maintain in m/s - */ - float speed_m_s; - /** - * @brief - */ - uint16_t time_since_start_s; -}; - -struct RepeatedSMSegment { - /** properties */ - std::vector speed_mileage_segments; - uint16_t time_since_start_s; -}; - - -using config_getter = std::function; -using report = TrackStateReportCollection::Report; -/** - * @brief provide `clock::now()` as the method to get the current timestamp - */ -using clock = app::utils::clock_t; - -const char *to_str(AccelerationProfile profile); - -/** - * @brief describes an acceleration process - */ -struct accel_calc_result_t { - float target_speed_m_s; - /** - * @brief acceleration in m/s^2; when 0, no acceleration is applied - * @note calculated at switching stage; note that it's an absolute value - * if the `target_speed_m_s` is higher than the current speed, - * the acceleration is positive, otherwise negative - * - * basically, depending the distance to the next segment, - * an acceleration rule is applied to the track - * (i.e. when to accelerate, and how fast to accelerate) - * it should be linear acceleration though - * - * if this value changed dynamically, it could be used as lerp - */ - float acceleration_m_s_2; -}; - -void set_global_config_getter(config_getter getter); -const TrackConfig &global_config(); - -const char *to_str(TrackState status); -const char *to_str(TrackControllerMode mode); -const char *to_str(TrackPidStageKind kind); -} - -#endif /* E32F663C_9C5C_4317_882A_E6457E88B576 */ diff --git a/src/esp/app_track_decoder.cpp b/src/esp/app_track_decoder.cpp deleted file mode 100644 index 498e0a5..0000000 --- a/src/esp/app_track_decoder.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "app_track_decoder.hpp" - -#include -#include -#include - -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(proto.data.bytes, proto.data.size); - std::ranges::copy(data, std::back_inserter(scheme.binary)); - return scheme; -} - -TrackSchemeDecoder::proto_type TrackSchemeDecoder::to_proto() const { - proto_type proto = track_app_TrackScheme_init_default; - proto.id = id; - proto.has_color = true; - proto.color = color.to_proto(); - const auto data = std::span(proto.data.bytes); - const auto count = std::min(data.size(), binary.size()); - std::ranges::copy(binary.begin(), binary.begin() + static_cast(count), data.begin()); - proto.data.size = count; - return proto; -} - -expected TrackSchemeDecoder::decode_core() const { - using ue = unexpected; - 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::Add::proto_type TrackSchemeMgr::Add::to_proto() const { - proto_type proto = track_app_TrackSchemeMgrAdd_init_default; - proto.has_scheme = true; - proto.scheme = scheme_decoder.to_proto(); - return proto; -} - -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; -} - -TrackSchemeMgr::proto_type TrackSchemeMgr::to_proto() const { - proto_type proto = track_app_TrackSchemeMgr_init_default; - std::visit(app::utils::overloads{ - [](Unknown) {}, - [&](const Add &add) { - proto.which_msg = track_app_TrackSchemeMgr_add_tag; - proto.msg.add = add.to_proto(); - }, - [&](Clear) { - proto.which_msg = track_app_TrackSchemeMgr_clear_tag; - proto.msg.clear = track_app_TrackSchemeMgrClear_init_default; - }}, - choice); - return proto; -} - -} // namespace app::track diff --git a/src/esp/app_track_drawer.cpp b/src/esp/app_track_drawer.cpp deleted file mode 100644 index 06adad9..0000000 --- a/src/esp/app_track_drawer.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "app_track_drawer.hpp" - -#include -#include - -#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_DISPLAY: - return track_core::TrackState::test_display; - } - 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(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(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 diff --git a/src/esp/app_track_model.cpp b/src/esp/app_track_model.cpp deleted file mode 100644 index 1cd326c..0000000 --- a/src/esp/app_track_model.cpp +++ /dev/null @@ -1,107 +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_DISPLAY: - return "TEST_DISPLAY"; - } - 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); - } -} -}