diff --git a/Core/UI/Data/DataAggregator.hpp b/Core/UI/Data/DataAggregator.hpp index 2abf7876..e622540b 100644 --- a/Core/UI/Data/DataAggregator.hpp +++ b/Core/UI/Data/DataAggregator.hpp @@ -10,9 +10,10 @@ #include #include "ApplicationTypes.h" #include "DataAggregatorWrapperType.h" -#include "BarDataCollection.h" +#include "DataQueue.hpp" #include "ObservedObject.hpp" +#include "ObservedDataQueue.hpp" using namespace std; @@ -23,18 +24,19 @@ using namespace std; */ class DataAggregator { public: - explicit DataAggregator(uint8_t lapEfficiencySize): lapEfficiencies({BarDataCollection(lapEfficiencySize)}) { - - } - + explicit DataAggregator(uint8_t motorVelocitiesSize, uint8_t batteryVoltagesSize, uint8_t lapEfficienciesSize, uint8_t lapTimesSize): + motorVelocities(motorVelocitiesSize), + batteryVoltages(batteryVoltagesSize), + lapEfficiencies(lapEfficienciesSize), + lapTimes(lapTimesSize) {} /** The observed object that holds the motor RPM data. */ - ObservedObject motorRPM{0}; + ObservedDataQueue motorVelocities; /** The observed object that holds the battery voltage data. */ - ObservedObject batteryVoltage{0}; + ObservedDataQueue batteryVoltages; /** The observed object that holds the current lap time. */ - ObservedObject lapTime{0}; + ObservedDataQueue lapTimes; /** The observed object that holds a collection of lap efficiencies. */ - ObservedObject> lapEfficiencies; + ObservedDataQueue lapEfficiencies; }; /** Returns a reference to the data aggregator object from a given wrapper. diff --git a/Core/UI/Data/DataAggregatorWrapper.cpp b/Core/UI/Data/DataAggregatorWrapper.cpp index 34088edd..a4bef44b 100644 --- a/Core/UI/Data/DataAggregatorWrapper.cpp +++ b/Core/UI/Data/DataAggregatorWrapper.cpp @@ -8,24 +8,25 @@ struct DataAggregatorWrapper { DataAggregator aggregator; - explicit DataAggregatorWrapper(uint8_t lapEfficiencySize): aggregator(lapEfficiencySize) {} + explicit DataAggregatorWrapper(uint8_t motorVelocitiesSize, uint8_t batteryVoltagesSize, uint8_t lapEfficienciesSize, uint8_t lapTimesSize): + aggregator(motorVelocitiesSize, batteryVoltagesSize, lapEfficienciesSize, lapTimesSize) {} }; -DataAggregatorWrapper* DataAggregator_Create(uint8_t lapEfficiencySize) { - auto* wrapper = new DataAggregatorWrapper(lapEfficiencySize); +DataAggregatorWrapper* DataAggregator_Create(uint8_t motorVelocitiesSize, uint8_t batteryVoltagesSize, uint8_t lapEfficienciesSize, uint8_t lapTimesSize) { + auto* wrapper = new DataAggregatorWrapper(motorVelocitiesSize, batteryVoltagesSize, lapEfficienciesSize, lapTimesSize); return wrapper; } void SetMotorRPM(DataAggregatorWrapper* wrapper, velocity_t rpm) { - wrapper->aggregator.motorRPM.set(rpm); + wrapper->aggregator.motorVelocities.add(rpm); } void SetBatteryVoltage(DataAggregatorWrapper* wrapper, voltage_t voltage) { - wrapper->aggregator.batteryVoltage.set(voltage); + wrapper->aggregator.batteryVoltages.add(voltage); } void SetLapTime(DataAggregatorWrapper* wrapper, ms_t time) { - wrapper->aggregator.lapTime.set(time); + wrapper->aggregator.lapTimes.add(time); } DataAggregator& DataAggregator_GetReference(DataAggregatorWrapper* wrapper) { diff --git a/Core/UI/Data/DataAggregatorWrapper.h b/Core/UI/Data/DataAggregatorWrapper.h index 8f46c67e..b9d3387f 100644 --- a/Core/UI/Data/DataAggregatorWrapper.h +++ b/Core/UI/Data/DataAggregatorWrapper.h @@ -16,7 +16,7 @@ extern "C" { * Creates a data aggregator wrapper object and returns a pointer to it. * @return A pointer to the data aggregator wrapper object. */ -DataAggregatorWrapper* DataAggregator_Create(uint8_t lapEfficiencySize); +DataAggregatorWrapper* DataAggregator_Create(uint8_t motorVelocitiesSize, uint8_t batteryVoltagesSize, uint8_t lapEfficienciesSize, uint8_t lapTimesSize); /** @ingroup core-modules * Sets the motor RPM data in the data aggregator object from a given wrapper. diff --git a/Core/UI/HomeView.cpp b/Core/UI/HomeView.cpp index a9088918..a267bbbd 100644 --- a/Core/UI/HomeView.cpp +++ b/Core/UI/HomeView.cpp @@ -40,9 +40,9 @@ HomeView::HomeView(lv_obj_t* parent, HomeViewModel& viewModel) : View(parent, vi lv_chart_set_type(lapTimeBarGraph, LV_CHART_TYPE_BAR); lv_obj_set_size(lapTimeBarGraph, 200, 150); lv_obj_center(lapTimeBarGraph); - lv_chart_set_range(lapTimeBarGraph, LV_CHART_AXIS_PRIMARY_Y, 0, 100); - lv_chart_set_range(lapTimeBarGraph, LV_CHART_AXIS_SECONDARY_Y, 0, 400); - lv_chart_set_point_count(lapTimeBarGraph, 12); + lv_chart_set_range(lapTimeBarGraph, LV_CHART_AXIS_PRIMARY_Y, 0, 1010); + lv_chart_set_range(lapTimeBarGraph, LV_CHART_AXIS_SECONDARY_Y, 0, 1010); +// lv_chart_set_point_count(lapTimeBarGraph, 12); /*Add ticks and label to every axis*/ lv_chart_set_axis_tick(lapTimeBarGraph, LV_CHART_AXIS_PRIMARY_X, 10, 5, 12, 3, true, 40); @@ -50,72 +50,31 @@ HomeView::HomeView(lv_obj_t* parent, HomeViewModel& viewModel) : View(parent, vi lv_chart_set_axis_tick(lapTimeBarGraph, LV_CHART_AXIS_SECONDARY_Y, 10, 5, 3, 4, true, 50); /*Zoom in a little in X*/ - lv_chart_set_zoom_x(lapTimeBarGraph, 800); +// lv_chart_set_zoom_x(lapTimeBarGraph, 800); /*Add two data series*/ lv_chart_series_t * ser1 = lv_chart_add_series(lapTimeBarGraph, lv_palette_lighten(LV_PALETTE_GREEN, 2), LV_CHART_AXIS_PRIMARY_Y); - lv_coord_t * ser1_array = lv_chart_get_y_array(lapTimeBarGraph, ser1); - /*Directly set points on 'ser2'*/ - - lv_chart_set_next_value(lapTimeBarGraph , ser1, 31); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 66); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 89); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 63); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 56); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 32); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 35); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 57); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 85); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 22); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 58); - - /* ser1_array[0] = 92; - ser1_array[1] = 71; - ser1_array[2] = 61; - ser1_array[3] = 15; - ser1_array[4] = 21; - ser1_array[5] = 35; - ser1_array[6] = 35; - ser1_array[7] = 58; - ser1_array[8] = 31; - ser1_array[9] = 53; - ser1_array[10] = 33; - ser1_array[11] = 73; */ - - /* ser1_array = lapEfficiencies.GetValues(); */ - + lv_chart_set_point_count(lapTimeBarGraph, viewModel.GetAggregator().lapTimes.getNumberOfElements()); + lv_chart_set_ext_y_array(lapTimeBarGraph, ser1, + reinterpret_cast(viewModel.GetAggregator().lapTimes.getValues())); lv_chart_refresh(lapTimeBarGraph); /*Required after direct set*/ - - viewModel.GetAggregator().batteryVoltage.addListener([this](const voltage_t& value) { - uint32_t n = value * 33 * 185 / 40960; + viewModel.GetAggregator().batteryVoltages.addListenerForLatest([this](const voltage_t& voltage) { + uint32_t n = voltage * 33 * 185 / 40960; lv_label_set_text_fmt(batteryVoltageLabel, "%d.%d Volts", n / 10, n % 10); }); - viewModel.GetAggregator().motorRPM.addListener([this](const velocity_t& value) { - lv_label_set_text_fmt(motorRPMLabel, "%d RPM", value); + viewModel.GetAggregator().motorVelocities.addListenerForLatest([this](const velocity_t& velocity) { + lv_label_set_text_fmt(motorRPMLabel, "%d RPM", velocity); }); - viewModel.GetAggregator().lapTime.addListener([this](const ms_t& value) { - lv_label_set_text_fmt(lapTimeLabel, "%dm %ds", value / 60000, value / 1000); + viewModel.GetAggregator().lapTimes.addListenerForLatest([this](const ms_t& time) { + lv_label_set_text_fmt(lapTimeLabel, "%dm %ds", time / 60000, time / 1000); }); - viewModel.GetAggregator().lapEfficiencies.addListener([this, ser1](const BarDataCollection &value) { - lv_chart_set_next_value(lapTimeBarGraph , ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); - lv_chart_set_next_value(lapTimeBarGraph, ser1, 10); + viewModel.GetAggregator().lapTimes.addListener([this](const DataQueue& queue) { + lv_chart_set_point_count(lapTimeBarGraph, queue.getNumberOfElements()); lv_chart_refresh(lapTimeBarGraph); - }); - + }); } diff --git a/Core/UI/StatsView.cpp b/Core/UI/StatsView.cpp index 3c5e6527..b2bd59aa 100644 --- a/Core/UI/StatsView.cpp +++ b/Core/UI/StatsView.cpp @@ -35,8 +35,8 @@ StatsView::StatsView(lv_obj_t* parent, StatsViewModel& viewModel) : View(parent, lv_label_set_text_fmt(rpmLabel, "%d RPM", rpm); }; - combiner = new CombineLatest(callback, viewModel.GetAggregator().motorRPM, - viewModel.GetAggregator().batteryVoltage); +// combiner = new CombineLatest(callback, viewModel.GetAggregator().motorVelocities, +// viewModel.GetAggregator().batteryVoltages); } StatsView::~StatsView() { diff --git a/Core/UI/Utils/BarDataCollection.h b/Core/UI/Utils/DataQueue.hpp similarity index 54% rename from Core/UI/Utils/BarDataCollection.h rename to Core/UI/Utils/DataQueue.hpp index ae2f792b..286091a3 100644 --- a/Core/UI/Utils/BarDataCollection.h +++ b/Core/UI/Utils/DataQueue.hpp @@ -2,30 +2,31 @@ // Created by Jeremy Cote on 2023-08-18. // -#ifndef UOSM_DASHBOARD_BARDATACOLLECTION_H -#define UOSM_DASHBOARD_BARDATACOLLECTION_H +#ifndef UOSM_DASHBOARD_DATAQUEUE_HPP +#define UOSM_DASHBOARD_DATAQUEUE_HPP #include -#include "lvgl/lvgl.h" - /** @ingroup core-ui-utils * A class that aggregates the data to display on a bar chart. * It can store any type T, but the values will be cast to lv_coord_t when displayed in a bar chart. + * Follows FIFO model. */ template -class BarDataCollection { +class DataQueue { private: - T** values; + T* values; + uint8_t largest; uint8_t head; uint8_t size; public: - explicit BarDataCollection(uint8_t size): size(size), head(0) { + explicit DataQueue(uint8_t size): size(size), head(0) { if (size <= 0) { throw std::invalid_argument("Size must be at least 1"); } - values = new T*[size]; + values = new T[size]; + largest = 0; } /** @@ -43,7 +44,26 @@ class BarDataCollection { * Use to set the data source of a bar chart. * @return a pointer to the underlying data source */ - [[nodiscard]] T** getValues() const { return values; } + [[nodiscard]] T* getValues() const { return values; } + + /** + * @return a pointer to the newest value added to the collection. + */ + [[nodiscard]] T getLatestValue() const noexcept(false) { + if (head == 0) { + throw std::out_of_range("DataQueue is empty."); + } + + return values[head - 1]; + } + + [[nodiscard]] T getLargestValue() const noexcept(false) { + if (head == 0) { + throw std::out_of_range("DataQueue is empty."); + } + + return values[largest]; + } /** * Copy a value into the bar data collection. @@ -52,14 +72,18 @@ class BarDataCollection { */ void add(T value) { if (head == size) { - delete values[0]; for (uint8_t i = 1; i < size; i++) { values[i - 1] = values[i]; } head--; } - values[head] = new T(value); + values[head] = value; + + if (value > values[largest]) { + largest = head; + } + head++; } @@ -77,12 +101,24 @@ class BarDataCollection { uint8_t i = head - 1; - if (values[i] != nullptr) { - delete values[i]; + bool shouldScanForLargest = value < values[i]; + + values[i] = value; + + for (int n = 0; n < head; n++) { + printf("%d: %d, ", n, values[n]); } - values[i] = new T(value); + if (shouldScanForLargest) { + for (int n = 0; n < head; n++) { + if (values[n] > values[largest]) { + largest = n; + } + } + } else if (value > values[largest]) { + largest = i; + } } }; -#endif //UOSM_DASHBOARD_BARDATACOLLECTION_H +#endif //UOSM_DASHBOARD_DATAQUEUE_HPP diff --git a/Core/UI/Utils/ObservedDataQueue.hpp b/Core/UI/Utils/ObservedDataQueue.hpp new file mode 100644 index 00000000..555a1021 --- /dev/null +++ b/Core/UI/Utils/ObservedDataQueue.hpp @@ -0,0 +1,71 @@ +// +// Created by Jeremy Cote on 2023-08-21. +// + +#ifndef UOSM_DASHBOARD_SDL_OBSERVEDDATAQUEUE_HPP +#define UOSM_DASHBOARD_SDL_OBSERVEDDATAQUEUE_HPP + +#include "DataQueue.hpp" +#include "ObservedObject.hpp" + +/** + * + */ +template +class ObservedDataQueue: public ObservedObject> { +private: + DataQueue queue; +public: + explicit ObservedDataQueue(uint8_t size): queue(size), ObservedObject>(&queue, false) {} + + /** + * Copy a value into the bar data collection. IMPORTANT: This creates a copy of the passed value. + * @param value to copy into the collection + */ + void add(T value) { + queue.add(value); + this->publish(); + } + + /** + * Update the last element of the collection. Useful if the latest value is still changing. + * If the collection is empty, this acts as a call to add + * @param value + */ + void update(T value) { + queue.update(value); + this->publish(); + } + + [[nodiscard]] uint8_t getSize() const { return queue.getSize(); } + + /** + * @return the number of values stored in the collection. + */ + [[nodiscard]] uint8_t getNumberOfElements() const { return queue.getNumberOfElements(); } + + /** + * Return the underlying data of the collection. + * Use to set the data source of a bar chart. + * @return a pointer to the underlying data source + */ + [[nodiscard]] T* getValues() const { return queue.getValues(); } + + /** + * @return a pointer to the newest value added to the collection. + */ + [[nodiscard]] T getLatestValue() const noexcept(false) { return queue.getLatestValue(); } + + /** + * Add a listener that only receives the latest value in the queue + * @param callback + * @return + */ + ObserverToken addListenerForLatest(std::function callback) { + return this->addListener([this, callback](const DataQueue& q) { + callback(queue.getLatestValue()); + }); + } +}; + +#endif //UOSM_DASHBOARD_SDL_OBSERVEDDATAQUEUE_HPP diff --git a/Core/UI/Utils/ObservedObject.hpp b/Core/UI/Utils/ObservedObject.hpp index 50b91cb7..28d3a0e3 100644 --- a/Core/UI/Utils/ObservedObject.hpp +++ b/Core/UI/Utils/ObservedObject.hpp @@ -61,22 +61,36 @@ class ObservedObject { * * @param value The initial value of the object. */ - explicit ObservedObject(const T& value) : value(value) {} + explicit ObservedObject(const T& value) { + this->value = new T(value); + } + + /** + * @brief Const a new ObservedObject where the observed object is held externally + * @param value + */ + explicit ObservedObject(T* value, bool makeCopy) { + this->value = value; + } + + ~ObservedObject() { + delete value; + } /** * @brief Get the current value of the object. * * @return T The current value of the object. */ - T get() const { return value; } + T& get() const { return *value; } /** - * @brief Get a reference to the current value of the observed object. + * @brief Get a pointer to the current value of the observed object. * Make sure to call publish on the observed object once your changes are complete to notify listeners. * This is useful when the observed object is a class. * @return a mute */ - T& getMutable() { return &value; } + T& getMutable() { return *value; } /** * @brief Set a new value for the object and notify the listeners if the value is different from the previous one. @@ -84,8 +98,8 @@ class ObservedObject { * @param value The new value for the object. */ void set(const T& value) { - if (this->value != value) { - this->value = value; + if (*(this->value) != value) { + this->value = new T(value); notify(); } } @@ -124,7 +138,7 @@ class ObservedObject { /** * @brief The value that the object holds */ - T value; + T* value; /** * @brief The list of registered listeners */ @@ -135,7 +149,7 @@ class ObservedObject { */ void notify() { for (const auto& listener: listeners) { - listener.onChange(value); + listener.onChange(*value); } } }; diff --git a/UOSM-Core b/UOSM-Core index 0eb5c98f..5d9f0a07 160000 --- a/UOSM-Core +++ b/UOSM-Core @@ -1 +1 @@ -Subproject commit 0eb5c98f3586f3d24899211913229f2eae260ec3 +Subproject commit 5d9f0a07f4afd3019bcc4926ee118a4a7ce12f93