working demo driver view
This commit is contained in:
parent
938978f5d4
commit
1e378d1093
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project("FT22 Steering Wheel Display")
|
||||
|
||||
@ -60,6 +60,9 @@ add_executable(
|
||||
src/AMI.cpp
|
||||
src/widgets.cpp
|
||||
src/util.cpp
|
||||
src/DriverView.cpp
|
||||
src/BaseDataSource.cpp
|
||||
src/DemoDataSource.cpp
|
||||
)
|
||||
target_include_directories(
|
||||
stw-display PUBLIC
|
||||
@ -78,4 +81,4 @@ target_link_libraries(
|
||||
${I2C_LIBRARY}
|
||||
fmt
|
||||
spdlog::spdlog)
|
||||
add_dependencies(stw-display fmt)
|
||||
add_dependencies(stw-display fmt)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "AMI.h"
|
||||
#include "AppState.h"
|
||||
#include "DriverView.h"
|
||||
#include "MissionSelect.h"
|
||||
#include "defines.h"
|
||||
|
||||
@ -44,6 +45,7 @@ private:
|
||||
std::unique_ptr<AppState> state;
|
||||
std::unique_ptr<MissionSelect> mission_select;
|
||||
std::unique_ptr<AMI> ami;
|
||||
std::unique_ptr<DriverView> driver_view;
|
||||
|
||||
bool running;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DemoDataSource.h"
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include <optional>
|
||||
@ -32,6 +34,7 @@ public:
|
||||
|
||||
AppView get_view() const;
|
||||
Mission get_mission() const;
|
||||
DemoDataSource get_data_source() const;
|
||||
|
||||
private:
|
||||
void unmarshal_mission_select(uint8_t* data);
|
||||
@ -42,6 +45,7 @@ private:
|
||||
|
||||
AppView view;
|
||||
Mission mission;
|
||||
DemoDataSource data_source;
|
||||
|
||||
#ifndef NDEBUG
|
||||
friend class KeyboardHandler;
|
||||
|
34
include/BaseDataSource.h
Normal file
34
include/BaseDataSource.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
class BaseDataSource {
|
||||
public:
|
||||
double get_speed(); // in km/h
|
||||
double get_brake_balance(); // 0.0 - 1.0
|
||||
double get_hv_voltage(); // in V
|
||||
double get_hv_voltage_ratio(); // 0.0 - 1.0
|
||||
double get_lv_voltage(); // in V
|
||||
double get_battery_temperature(); // in °C
|
||||
double get_throttle_ratio(); // 0.0 - 1.0
|
||||
double get_brake_pressure_front_bar(); // in bar
|
||||
double get_brake_pressure_rear_bar(); // in bar
|
||||
|
||||
bool brake_balance_change_is_recent(); // true if the last change was less
|
||||
// than 1 second ago
|
||||
|
||||
virtual void poll() = 0;
|
||||
|
||||
protected:
|
||||
void set_brake_balance(double value);
|
||||
|
||||
double speed;
|
||||
double brake_balance;
|
||||
double hv_voltage;
|
||||
double hv_voltage_ratio;
|
||||
double lv_voltage;
|
||||
double battery_temperature;
|
||||
double throttle_ratio;
|
||||
double brake_pressure_front_bar;
|
||||
double brake_pressure_rear_bar;
|
||||
|
||||
int time_of_last_brake_balance_change;
|
||||
};
|
13
include/DemoDataSource.h
Normal file
13
include/DemoDataSource.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseDataSource.h"
|
||||
|
||||
class DemoDataSource final : public BaseDataSource {
|
||||
public:
|
||||
DemoDataSource();
|
||||
~DemoDataSource();
|
||||
|
||||
void poll() override;
|
||||
void bump_brake_balance_up();
|
||||
void bump_brake_balance_down();
|
||||
};
|
30
include/DriverView.h
Normal file
30
include/DriverView.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "View.h"
|
||||
#include "defines.h"
|
||||
#include "util.h"
|
||||
#include "widgets.h"
|
||||
|
||||
class DriverView final : public View {
|
||||
public:
|
||||
DriverView(SDL_Renderer* renderer);
|
||||
~DriverView();
|
||||
|
||||
void draw(const AppState& state) override;
|
||||
|
||||
private:
|
||||
TTF_Font* font_large;
|
||||
TTF_Font* font_detail;
|
||||
TTF_Font* font_tiny;
|
||||
TTF_Font* font_giant;
|
||||
TTF_Font* font_medium;
|
||||
std::unique_ptr<TextWidget> speed_widget;
|
||||
std::unique_ptr<TextWidget> speed_hint_widget;
|
||||
|
||||
std::unique_ptr<TextWidget> brake_balance_widget;
|
||||
std::unique_ptr<TextWidget> brake_balance_hint_widget;
|
||||
|
||||
std::unique_ptr<TextWidget> general_info_widget;
|
||||
|
||||
std::unique_ptr<TextWidget> focus_widget;
|
||||
std::unique_ptr<TextWidget> focus_hint_widget;
|
||||
};
|
@ -79,12 +79,13 @@ public:
|
||||
~TextWidget();
|
||||
|
||||
void update_text(const std::string& new_text);
|
||||
void set_wrap_width(int width);
|
||||
|
||||
protected:
|
||||
TTF_Font* font;
|
||||
|
||||
std::string text;
|
||||
|
||||
int wrap_width = -1; // -1 means no wrapping (default behavior)
|
||||
SDL_Texture* generate_text(const std::string& text);
|
||||
};
|
||||
|
||||
|
82
src/App.cpp
82
src/App.cpp
@ -2,6 +2,8 @@
|
||||
|
||||
#include "AMI.h"
|
||||
#include "AppState.h"
|
||||
#include "DemoDataSource.h"
|
||||
#include "DriverView.h"
|
||||
#include "MissionSelect.h"
|
||||
#include "events.h"
|
||||
|
||||
@ -15,6 +17,7 @@
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
@ -60,6 +63,11 @@ void App::init_state() {
|
||||
int App::run() {
|
||||
mission_select = std::make_unique<MissionSelect>(renderer);
|
||||
ami = std::make_unique<AMI>(renderer);
|
||||
driver_view = std::make_unique<DriverView>(renderer);
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::cout << "Change brake balance with W (+) and S (-)" << std::endl;
|
||||
#endif
|
||||
|
||||
running = true;
|
||||
|
||||
@ -110,6 +118,9 @@ void App::render() {
|
||||
case AppView::AMI:
|
||||
ami->draw(*state);
|
||||
break;
|
||||
case AppView::DRIVER:
|
||||
driver_view->draw(*state);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("Unknown view: {}", view));
|
||||
}
|
||||
@ -138,39 +149,44 @@ SDLManager::~SDLManager() {
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
/*
|
||||
Handle key presses
|
||||
*/
|
||||
void KeyboardHandler::handle_keyup(const SDL_Event& ev, AppState& state) {
|
||||
switch (ev.key.keysym.sym) {
|
||||
case SDLK_DOWN:
|
||||
case SDLK_RIGHT:
|
||||
if (state.mission == Mission::MANUAL) {
|
||||
state.mission = Mission::ACCELERATION;
|
||||
} else {
|
||||
state.mission = static_cast<Mission>(static_cast<int>(state.mission) + 1);
|
||||
auto pressed_key = ev.key.keysym.sym;
|
||||
if (state.view == AppView::MISSION_SELECT) {
|
||||
if (pressed_key == SDLK_DOWN || pressed_key == SDLK_RIGHT) {
|
||||
auto new_mission = static_cast<int>(state.mission) + 1;
|
||||
if (new_mission > 7) {
|
||||
new_mission = 1;
|
||||
}
|
||||
std::cout << "New mission: " << new_mission << std::endl;
|
||||
state.mission = static_cast<Mission>(new_mission);
|
||||
} else if (pressed_key == SDLK_UP || pressed_key == SDLK_LEFT) {
|
||||
auto new_mission = static_cast<int>(state.mission) - 1;
|
||||
if (new_mission < 1) {
|
||||
new_mission = 7;
|
||||
}
|
||||
state.mission = static_cast<Mission>(new_mission);
|
||||
} else if (pressed_key == SDLK_RETURN) {
|
||||
|
||||
if (state.mission == Mission::MANUAL) {
|
||||
state.view = AppView::DRIVER;
|
||||
} else {
|
||||
state.view = AppView::AMI;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDLK_UP:
|
||||
case SDLK_LEFT:
|
||||
if (state.mission == Mission::NONE ||
|
||||
state.mission == Mission::ACCELERATION) {
|
||||
state.mission = Mission::MANUAL;
|
||||
} else {
|
||||
state.mission = static_cast<Mission>(static_cast<int>(state.mission) - 1);
|
||||
}
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
switch (state.view) {
|
||||
case AppView::MISSION_SELECT:
|
||||
state.view = AppView::AMI;
|
||||
break;
|
||||
case AppView::AMI:
|
||||
case AppView::DRIVER:
|
||||
case AppView::TESTING:
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("Unknown view: {}", state.view));
|
||||
}
|
||||
break;
|
||||
// We can just ignore other keypresses, so no need for a default clause
|
||||
}
|
||||
}
|
||||
|
||||
if (state.view == AppView::AMI || state.view == AppView::DRIVER) {
|
||||
if (pressed_key == SDLK_ESCAPE) {
|
||||
state.view = AppView::MISSION_SELECT;
|
||||
}
|
||||
}
|
||||
|
||||
if (pressed_key == SDLK_w) {
|
||||
state.data_source.bump_brake_balance_up();
|
||||
} else if (pressed_key == SDLK_s) {
|
||||
state.data_source.bump_brake_balance_down();
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ AppState::~AppState() {
|
||||
}
|
||||
|
||||
void AppState::poll() {
|
||||
data_source.poll();
|
||||
if (!i2c_dev_file) {
|
||||
return;
|
||||
}
|
||||
@ -112,4 +113,6 @@ void AppState::unmarshal_mission_select(uint8_t* data) {
|
||||
void AppState::unmarshal_ami(uint8_t* data) {
|
||||
mission = static_cast<Mission>(data[1]);
|
||||
spdlog::info("Mission after poll: {}", mission);
|
||||
}
|
||||
}
|
||||
|
||||
DemoDataSource AppState::get_data_source() const { return data_source; }
|
35
src/BaseDataSource.cpp
Normal file
35
src/BaseDataSource.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "BaseDataSource.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
double BaseDataSource::get_speed() { return speed; }
|
||||
|
||||
double BaseDataSource::get_brake_balance() { return brake_balance; }
|
||||
|
||||
double BaseDataSource::get_hv_voltage() { return hv_voltage; }
|
||||
|
||||
double BaseDataSource::get_hv_voltage_ratio() { return hv_voltage_ratio; }
|
||||
|
||||
double BaseDataSource::get_lv_voltage() { return lv_voltage; }
|
||||
|
||||
double BaseDataSource::get_battery_temperature() { return battery_temperature; }
|
||||
|
||||
double BaseDataSource::get_throttle_ratio() { return throttle_ratio; }
|
||||
|
||||
double BaseDataSource::get_brake_pressure_front_bar() {
|
||||
return brake_pressure_front_bar;
|
||||
}
|
||||
|
||||
double BaseDataSource::get_brake_pressure_rear_bar() {
|
||||
return brake_pressure_rear_bar;
|
||||
}
|
||||
|
||||
void BaseDataSource::set_brake_balance(double value) {
|
||||
brake_balance = value;
|
||||
time_of_last_brake_balance_change = SDL_GetTicks();
|
||||
}
|
||||
|
||||
bool BaseDataSource::brake_balance_change_is_recent() {
|
||||
|
||||
return time_of_last_brake_balance_change != 0 &&
|
||||
SDL_GetTicks() - time_of_last_brake_balance_change < 500;
|
||||
}
|
33
src/DemoDataSource.cpp
Normal file
33
src/DemoDataSource.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "DemoDataSource.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
DemoDataSource::DemoDataSource() {
|
||||
brake_balance = 0.5;
|
||||
|
||||
lv_voltage = 13.1;
|
||||
battery_temperature = 20.1;
|
||||
throttle_ratio = 0.01;
|
||||
brake_pressure_front_bar = 1;
|
||||
brake_pressure_rear_bar = 1;
|
||||
}
|
||||
|
||||
DemoDataSource::~DemoDataSource() {}
|
||||
|
||||
void DemoDataSource::poll() {
|
||||
int min_voltage = 150;
|
||||
int max_voltage = 480;
|
||||
static int count = 0;
|
||||
count++;
|
||||
hv_voltage = 220.1 + std::cos(count / 1000.0 + 2) * 50;
|
||||
hv_voltage_ratio = (hv_voltage - min_voltage) / (max_voltage - min_voltage);
|
||||
speed = 10 + std::sin(count / 100.0) * 5;
|
||||
}
|
||||
|
||||
void DemoDataSource::bump_brake_balance_up() {
|
||||
set_brake_balance(brake_balance + 0.01);
|
||||
}
|
||||
|
||||
void DemoDataSource::bump_brake_balance_down() {
|
||||
set_brake_balance(brake_balance - 0.01);
|
||||
}
|
124
src/DriverView.cpp
Normal file
124
src/DriverView.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "DriverView.h"
|
||||
|
||||
#include "DemoDataSource.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define LARGE_FONT_SIZE_POINTS 170
|
||||
#define GIANT_FONT_SIZE_POINTS 200
|
||||
#define MEDIUM_FONT_SIZE_POINTS 50
|
||||
#define SOC_BAR_HEIGHT 80
|
||||
#define SPEED_TEXT_X 22
|
||||
#define SPEED_TEXT_Y 50
|
||||
|
||||
DriverView::DriverView(SDL_Renderer* renderer)
|
||||
: View{renderer}, font_large{util::load_font("resources/Avenir-Book.ttf",
|
||||
LARGE_FONT_SIZE_POINTS)},
|
||||
font_detail{util::load_font("resources/Avenir-Book.ttf", 20)},
|
||||
font_tiny{util::load_font("resources/Avenir-Book.ttf", 15)},
|
||||
font_giant{
|
||||
util::load_font("resources/Avenir-Book.ttf", GIANT_FONT_SIZE_POINTS)},
|
||||
font_medium{util::load_font("resources/Avenir-Book.ttf",
|
||||
MEDIUM_FONT_SIZE_POINTS)} {
|
||||
speed_widget = std::make_unique<TextWidget>(renderer, font_large, "");
|
||||
speed_widget->set_position(SPEED_TEXT_X, SPEED_TEXT_Y);
|
||||
speed_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||
|
||||
speed_hint_widget = std::make_unique<TextWidget>(renderer, font_tiny, "SPD");
|
||||
speed_hint_widget->set_position(20, 210);
|
||||
speed_hint_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||
|
||||
brake_balance_widget = std::make_unique<TextWidget>(renderer, font_large, "");
|
||||
brake_balance_widget->set_position(SPEED_TEXT_X + SCREEN_WIDTH / 2,
|
||||
SPEED_TEXT_Y);
|
||||
brake_balance_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||
brake_balance_hint_widget =
|
||||
std::make_unique<TextWidget>(renderer, font_tiny, "BB");
|
||||
brake_balance_hint_widget->set_position(20 + SCREEN_WIDTH / 2, 210);
|
||||
brake_balance_hint_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||
|
||||
general_info_widget = std::make_unique<TextWidget>(renderer, font_detail, "");
|
||||
general_info_widget->set_position(SPEED_TEXT_X, SPEED_TEXT_Y + 200);
|
||||
general_info_widget->set_alignment(Alignment::LEFT, Alignment::TOP);
|
||||
general_info_widget->set_wrap_width(SCREEN_WIDTH - 50);
|
||||
|
||||
focus_widget = std::make_unique<TextWidget>(renderer, font_giant, "");
|
||||
focus_widget->set_position(120, 70);
|
||||
focus_widget->set_alignment(Alignment::LEFT, Alignment::TOP);
|
||||
focus_hint_widget = std::make_unique<TextWidget>(renderer, font_medium, "");
|
||||
focus_hint_widget->set_position(120, 40);
|
||||
// focus_hint_widget->set_alignment(Alignment::CENTER, Alignment::CENTER);
|
||||
}
|
||||
|
||||
DriverView::~DriverView() {}
|
||||
|
||||
void DriverView::draw(const AppState& state) {
|
||||
auto ds = state.get_data_source();
|
||||
auto hv_ratio = ds.get_hv_voltage_ratio();
|
||||
SDL_Rect rect;
|
||||
rect.x = 0;
|
||||
rect.y = 0;
|
||||
rect.w = SCREEN_WIDTH;
|
||||
rect.h = SCREEN_HEIGHT;
|
||||
|
||||
static auto hv_low = false;
|
||||
// hysteresis for the hv voltage low
|
||||
if (hv_ratio < 0.15) {
|
||||
hv_low = true;
|
||||
} else if (hv_ratio > 0.2) {
|
||||
hv_low = false;
|
||||
}
|
||||
|
||||
int red = hv_low ? 0xa0 : 0x30;
|
||||
SDL_SetRenderDrawColor(renderer, red, 0x30, 0x30, 255);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
|
||||
auto brake_balance_text = fmt::format("{:.0f}", ds.get_brake_balance() * 100);
|
||||
|
||||
if (ds.brake_balance_change_is_recent()) {
|
||||
SDL_SetRenderDrawColor(renderer, 255, 69, 100, 255);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
focus_widget->update_text(brake_balance_text);
|
||||
focus_widget->draw();
|
||||
focus_hint_widget->update_text("BBAL");
|
||||
focus_hint_widget->draw();
|
||||
return;
|
||||
}
|
||||
|
||||
rect.w = SCREEN_WIDTH * hv_ratio;
|
||||
rect.h = SOC_BAR_HEIGHT;
|
||||
SDL_SetRenderDrawColor(renderer, 76, 255, 0, 255);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
|
||||
speed_widget->update_text(fmt::format("{:02.0f}", ds.get_speed()));
|
||||
speed_widget->draw();
|
||||
speed_hint_widget->draw();
|
||||
|
||||
brake_balance_widget->update_text(brake_balance_text);
|
||||
|
||||
brake_balance_widget->draw();
|
||||
brake_balance_hint_widget->draw();
|
||||
|
||||
// draw rectangles around the speed and brake balance widgets
|
||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 255);
|
||||
rect.x = 0;
|
||||
rect.y = SOC_BAR_HEIGHT;
|
||||
rect.w = SCREEN_WIDTH / 2;
|
||||
rect.h = 150;
|
||||
SDL_RenderDrawRect(renderer, &rect);
|
||||
rect.x = SCREEN_WIDTH / 2;
|
||||
SDL_RenderDrawRect(renderer, &rect);
|
||||
|
||||
auto general_string = fmt::format(
|
||||
"HV: {:.0f}V | LV: {:.1f}V | B_TEMP: {:.1f}C\nAPPS: {:.0f}% | BPF: "
|
||||
"{:.0f} bar | BPR: {:.0f} bar",
|
||||
ds.get_hv_voltage(), ds.get_lv_voltage(), ds.get_battery_temperature(),
|
||||
ds.get_throttle_ratio() * 100, ds.get_brake_pressure_front_bar(),
|
||||
ds.get_brake_pressure_rear_bar());
|
||||
|
||||
general_info_widget->update_text(general_string);
|
||||
general_info_widget->draw();
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#include "MissionSelect.h"
|
||||
|
||||
#include "AppState.h"
|
||||
#include "DriverView.h"
|
||||
#include "defines.h"
|
||||
#include "events.h"
|
||||
#include "util.h"
|
||||
|
@ -167,8 +167,13 @@ void TextWidget::update_text(const std::string& new_text) {
|
||||
}
|
||||
|
||||
SDL_Texture* TextWidget::generate_text(const std::string& text) {
|
||||
SDL_Surface* surf =
|
||||
TTF_RenderText_Solid(font, text.c_str(), {0xFF, 0xFF, 0XFF, 0xFF});
|
||||
SDL_Surface* surf;
|
||||
if (wrap_width == -1) {
|
||||
surf = TTF_RenderText_Blended(font, text.c_str(), {0xFF, 0xFF, 0XFF, 0xFF});
|
||||
} else {
|
||||
surf = TTF_RenderText_Blended_Wrapped(font, text.c_str(),
|
||||
{0xFF, 0xFF, 0XFF, 0xFF}, wrap_width);
|
||||
}
|
||||
if (surf == nullptr) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unable to render text surface: {}", TTF_GetError()));
|
||||
@ -181,6 +186,8 @@ SDL_Texture* TextWidget::generate_text(const std::string& text) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
void TextWidget::set_wrap_width(int width) { wrap_width = width; }
|
||||
|
||||
ListWidget::ListWidget(SDL_Renderer* renderer, int element_height,
|
||||
Alignment element_alignment)
|
||||
: Widget{renderer}, element_height{element_height},
|
||||
|
Loading…
x
Reference in New Issue
Block a user