working demo driver view

This commit is contained in:
Panagiotis Karagiannis 2022-07-09 11:50:10 +02:00
parent 938978f5d4
commit 1e378d1093
14 changed files with 345 additions and 39 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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
View 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
View 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
View 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;
};

View File

@ -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);
};

View File

@ -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();
}
}

View File

@ -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
View 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
View 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
View 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();
}

View File

@ -1,6 +1,7 @@
#include "MissionSelect.h"
#include "AppState.h"
#include "DriverView.h"
#include "defines.h"
#include "events.h"
#include "util.h"

View File

@ -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},