Implement AMI, more communication with STM etc

It's late and I forgot to commit
This commit is contained in:
Jasper Blanckenburg 2022-05-28 02:04:10 +02:00
parent c553ea36d4
commit 0cd0ce6f3e
12 changed files with 293 additions and 74 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.18)
project("FT22 Steering Wheel Display")
@ -57,6 +57,7 @@ add_executable(
src/AppState.cpp
src/View.cpp
src/MissionSelect.cpp
src/AMI.cpp
src/widgets.cpp
src/util.cpp
)

27
include/AMI.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include "AppState.h"
#include "View.h"
#include "widgets.h"
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_ttf.h>
#include <memory>
#include <unordered_map>
#include <vector>
class AMI final : public View {
public:
AMI(SDL_Renderer* renderer);
~AMI();
void draw(const AppState& state) override;
private:
TTF_Font* font_medium;
TTF_Font* font_large;
std::unique_ptr<TextWidget> header;
std::unordered_map<Mission, std::unique_ptr<TextWidget>> missions;
};

View File

@ -1,5 +1,6 @@
#pragma once
#include "AMI.h"
#include "AppState.h"
#include "MissionSelect.h"
#include "defines.h"
@ -14,6 +15,13 @@ public:
~SDLManager();
};
#ifndef NDEBUG
class KeyboardHandler {
public:
void handle_keyup(const SDL_Event& ev, AppState& state);
};
#endif // !NDEBUG
class App {
public:
App();
@ -35,9 +43,14 @@ private:
std::unique_ptr<AppState> state;
std::unique_ptr<MissionSelect> mission_select;
std::unique_ptr<AMI> ami;
bool running;
SDL_Window* window;
SDL_Renderer* renderer;
#ifndef NDEBUG
KeyboardHandler keyboard_handler;
#endif // !NDEBUG
};

View File

@ -11,6 +11,17 @@ constexpr int I2C_POLL_COMMAND = 0x00;
enum class AppView { MISSION_SELECT, AMI, DRIVER, TESTING };
std::ostream& operator<<(std::ostream& os, AppView view);
enum class Mission {
NONE,
ACCELERATION,
SKIDPAD,
AUTOCROSS,
TRACKDRIVE,
EBS_TEST,
INSPECTION,
MANUAL
};
std::ostream& operator<<(std::ostream& os, Mission mission);
class AppState {
public:
@ -19,11 +30,21 @@ public:
void poll();
AppView get_view();
AppView get_view() const;
Mission get_mission() const;
private:
void unmarshal_mission_select(uint8_t* data);
// This is optional so that we can still run the code on a PC without I2C
std::optional<int> i2c_dev_file;
AppView view;
};
Mission mission;
#ifndef NDEBUG
friend class KeyboardHandler;
#endif // !NDEBUG
};
extern AppState app_state;

View File

@ -1,5 +1,6 @@
#pragma once
#include "AppState.h"
#include "View.h"
#include "events.h"
#include "widgets.h"
@ -15,8 +16,7 @@ public:
MissionSelect(SDL_Renderer* renderer);
~MissionSelect();
void draw() override;
void handle_events(std::queue<Event>& events) override;
void draw(const AppState& state) override;
private:
TTF_Font* avenir;

View File

@ -1,5 +1,6 @@
#pragma once
#include "AppState.h"
#include "events.h"
#include <SDL2/SDL.h>
@ -11,8 +12,7 @@ public:
View(SDL_Renderer* renderer);
virtual ~View();
virtual void draw() = 0;
virtual void handle_events(std::queue<Event>& events) = 0;
virtual void draw(const AppState& state) = 0;
protected:
SDL_Renderer* renderer;

View File

@ -3,17 +3,30 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <fmt/ostream.h>
#include <optional>
#include <ostream>
#include <string>
#include <vector>
enum class Alignment { LEFT, RIGHT, CENTER };
enum class Alignment {
START,
END,
CENTER,
LEFT = START,
TOP = START,
RIGHT = END,
BOTTOM = END
};
std::ostream& operator<<(std::ostream& os, Alignment align);
struct PositionInfo {
PositionInfo();
int x;
int y;
Alignment align;
Alignment align_h;
Alignment align_v;
};
class Widget {
@ -24,7 +37,7 @@ public:
virtual void set_width(int width, bool preserve_aspect_ratio = true);
virtual void set_height(int height, bool preserve_aspect_ratio = true);
virtual void set_position(int x, int y);
virtual void set_alignment(Alignment align);
virtual void set_alignment(Alignment align_h, Alignment align_v);
int get_width();
int get_height();
@ -87,6 +100,7 @@ public:
void select_next();
void select_prev();
void select(size_t n);
size_t get_selection();
protected:

64
src/AMI.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "AMI.h"
#include "AppState.h"
#include "SDL_render.h"
#include "SDL_ttf.h"
#include "defines.h"
#include "util.h"
#include "widgets.h"
#include <memory>
constexpr const char* CHINAT_FONT_PATH = "resources/CHINAT.ttf";
constexpr int CHINAT_MEDIUM_PTS = 25;
constexpr int CHINAT_LARGE_PTS = 40;
constexpr int HEADER_Y = 10;
AMI::AMI(SDL_Renderer* renderer)
: View{renderer}, font_medium{util::load_font(CHINAT_FONT_PATH,
CHINAT_MEDIUM_PTS)},
font_large{util::load_font(CHINAT_FONT_PATH, CHINAT_LARGE_PTS)} {
header =
std::make_unique<TextWidget>(renderer, font_medium, "Current mission:");
header->set_position(SCREEN_WIDTH / 2, HEADER_Y);
header->set_alignment(Alignment::CENTER, Alignment::TOP);
missions.emplace(Mission::NONE,
std::make_unique<TextWidget>(renderer, font_large,
"NO MISSION SELECTED"));
missions.emplace(
Mission::ACCELERATION,
std::make_unique<TextWidget>(renderer, font_large, "ACCELERATION"));
missions.emplace(Mission::SKIDPAD, std::make_unique<TextWidget>(
renderer, font_large, "SKIDPAD"));
missions.emplace(Mission::AUTOCROSS, std::make_unique<TextWidget>(
renderer, font_large, "AUTOCROSS"));
missions.emplace(
Mission::TRACKDRIVE,
std::make_unique<TextWidget>(renderer, font_large, "TRACKDRIVE"));
missions.emplace(Mission::EBS_TEST, std::make_unique<TextWidget>(
renderer, font_large, "EBS TEST"));
missions.emplace(
Mission::INSPECTION,
std::make_unique<TextWidget>(renderer, font_large, "INSPECTION"));
missions.emplace(
Mission::MANUAL,
std::make_unique<TextWidget>(renderer, font_large, "MANUAL DRIVING"));
for (auto& [mission, widget] : missions) {
widget->set_position(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
widget->set_alignment(Alignment::CENTER, Alignment::CENTER);
}
}
AMI::~AMI() {
TTF_CloseFont(font_medium);
TTF_CloseFont(font_large);
}
void AMI::draw(const AppState& state) {
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer);
header->draw();
missions[state.get_mission()]->draw();
}

View File

@ -1,5 +1,6 @@
#include "App.h"
#include "AMI.h"
#include "AppState.h"
#include "MissionSelect.h"
#include "events.h"
@ -58,6 +59,7 @@ void App::init_state() {
int App::run() {
mission_select = std::make_unique<MissionSelect>(renderer);
ami = std::make_unique<AMI>(renderer);
running = true;
@ -91,40 +93,22 @@ void App::handle_events() {
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) {
running = false;
#ifndef NDEBUG
} else if (e.type == SDL_KEYUP) {
switch (e.key.keysym.sym) {
case SDLK_DOWN:
case SDLK_RIGHT:
events.push(Event::Next);
break;
case SDLK_UP:
case SDLK_LEFT:
events.push(Event::Prev);
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
events.push(Event::Confirm);
break;
// We can just ignore other keypresses, so no need for a default clause
}
keyboard_handler.handle_keyup(e, *state);
#endif // !NDEBUG
}
}
AppView view = state->get_view();
switch (view) {
case AppView::MISSION_SELECT:
mission_select->handle_events(events);
break;
default:
throw std::runtime_error(fmt::format("Unknown view: {}", view));
}
}
void App::render() {
AppView view = state->get_view();
switch (view) {
case AppView::MISSION_SELECT:
mission_select->draw();
mission_select->draw(*state);
break;
case AppView::AMI:
ami->draw(*state);
break;
default:
throw std::runtime_error(fmt::format("Unknown view: {}", view));
@ -152,4 +136,41 @@ SDLManager::~SDLManager() {
IMG_Quit();
TTF_Quit();
SDL_Quit();
}
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);
}
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
}
}

View File

@ -30,17 +30,40 @@ std::ostream& operator<<(std::ostream& os, AppView view) {
}
}
std::ostream& operator<<(std::ostream& os, Mission mission) {
switch (mission) {
case Mission::NONE:
return os << "NONE";
case Mission::ACCELERATION:
return os << "ACCELERATION";
case Mission::SKIDPAD:
return os << "SKIDPAD";
case Mission::AUTOCROSS:
return os << "AUTOCROSS";
case Mission::TRACKDRIVE:
return os << "TRACKDRIVE";
case Mission::EBS_TEST:
return os << "EBS_TEST";
case Mission::INSPECTION:
return os << "INSPECTION";
case Mission::MANUAL:
return os << "MANUAL";
default:
return os << "UNKNOWN_MISSION[" << static_cast<int>(mission) << "]";
}
}
AppState::AppState(const std::string& i2c_dev_path)
: view{AppView::MISSION_SELECT} {
: view{AppView::MISSION_SELECT}, mission{Mission::ACCELERATION} {
i2c_dev_file = open(i2c_dev_path.c_str(), O_RDWR);
if (i2c_dev_file < 0) {
spdlog::error("Couldn't open I2C device: {}", errno);
spdlog::error("Couldn't open I2C device: {}", strerror(errno));
i2c_dev_file = std::nullopt;
return;
}
if (ioctl(*i2c_dev_file, I2C_SLAVE, I2C_SLAVE_ADDR) < 0) {
spdlog::error("Couldn't set I2C slave address: {}", errno);
spdlog::error("Couldn't set I2C slave address: {}", strerror(errno));
close(*i2c_dev_file);
i2c_dev_file = std::nullopt;
}
@ -60,10 +83,25 @@ void AppState::poll() {
uint8_t data[32];
int count = i2c_smbus_read_block_data(*i2c_dev_file, I2C_POLL_COMMAND, data);
if (count < 0) {
throw std::runtime_error(fmt::format("Couldn't poll I2C slave: {}", errno));
throw std::runtime_error(
fmt::format("Couldn't poll I2C slave: {}", strerror(errno)));
}
view = static_cast<AppView>(data[0]);
spdlog::info("View after poll: {}", view);
switch (view) {
case AppView::MISSION_SELECT:
unmarshal_mission_select(data);
break;
default:
throw std::runtime_error(fmt::format("Unknown view: {}", view));
}
}
AppView AppState::get_view() { return view; }
AppView AppState::get_view() const { return view; }
Mission AppState::get_mission() const { return mission; }
void AppState::unmarshal_mission_select(uint8_t* data) {
mission = static_cast<Mission>(data[1]);
spdlog::info("Mission after poll: {}", mission);
}

View File

@ -1,5 +1,6 @@
#include "MissionSelect.h"
#include "AppState.h"
#include "defines.h"
#include "events.h"
#include "util.h"
@ -30,12 +31,12 @@ MissionSelect::MissionSelect(SDL_Renderer* renderer)
ft_logo = std::make_unique<ImageWidget>(renderer, FT_LOGO_PATH);
ft_logo->set_height(FT_LOGO_HEIGHT);
ft_logo->set_position(SCREEN_WIDTH / 2, 0);
ft_logo->set_alignment(Alignment::CENTER);
ft_logo->set_alignment(Alignment::CENTER, Alignment::TOP);
widgets.push_back(ft_logo.get());
choose = std::make_unique<TextWidget>(renderer, avenir, "Choose a mission:");
choose->set_position(SCREEN_WIDTH / 2, CHOOSE_Y);
choose->set_alignment(Alignment::CENTER);
choose->set_alignment(Alignment::CENTER, Alignment::TOP);
widgets.push_back(choose.get());
for (const auto mission : MISSIONS) {
@ -60,31 +61,13 @@ MissionSelect::~MissionSelect() {
TTF_CloseFont(chinat);
}
void MissionSelect::draw() {
void MissionSelect::draw(const AppState& state) {
size_t n = static_cast<int>(state.get_mission()) - 1;
missions_widget->select(n);
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer);
for (const auto& widget : widgets) {
widget->draw();
}
}
void MissionSelect::handle_events(std::queue<Event>& events) {
while (!events.empty()) {
Event e = events.front();
events.pop();
switch (e) {
case Event::Next:
missions_widget->select_next();
break;
case Event::Prev:
missions_widget->select_prev();
break;
case Event::Confirm:
std::cout << fmt::format("Selected mission {}\n",
missions_widget->get_selection());
break;
default:
throw std::runtime_error(fmt::format("Unknown event: {}", (int)e));
}
}
}

View File

@ -21,7 +21,21 @@ constexpr uint8_t LIST_NORMAL_BG_R = 0x00;
constexpr uint8_t LIST_NORMAL_BG_G = 0x00;
constexpr uint8_t LIST_NORMAL_BG_B = 0x00;
PositionInfo::PositionInfo() : x{0}, y{0}, align{Alignment::LEFT} {}
std::ostream& operator<<(std::ostream& os, Alignment align) {
switch (align) {
case Alignment::START:
return os << "START";
case Alignment::CENTER:
return os << "CENTER";
case Alignment::END:
return os << "END";
default:
return os << "UNKNOWN ALIGNMENT[" << static_cast<int>(align) << "]";
}
}
PositionInfo::PositionInfo()
: x{0}, y{0}, align_h{Alignment::LEFT}, align_v{Alignment::LEFT} {}
Widget::Widget(SDL_Renderer* renderer) : renderer{renderer}, rect{0, 0, 0, 0} {}
@ -51,8 +65,9 @@ void Widget::set_position(int x, int y) {
recalculate_pos();
}
void Widget::set_alignment(Alignment align) {
pos.align = align;
void Widget::set_alignment(Alignment align_h, Alignment align_v) {
pos.align_h = align_h;
pos.align_v = align_v;
recalculate_pos();
}
@ -65,19 +80,32 @@ const PositionInfo& Widget::get_position() { return pos; }
void Widget::recalculate_pos() {
int x = pos.x;
int y = pos.y;
switch (pos.align) {
case Alignment::LEFT:
switch (pos.align_h) {
case Alignment::START:
break;
case Alignment::RIGHT:
case Alignment::END:
x -= rect.w;
break;
case Alignment::CENTER:
x -= rect.w / 2;
break;
default:
throw std::runtime_error(
fmt::format("Unknown alignment: {}", (int)pos.align));
throw std::runtime_error(fmt::format("Unknown alignment: {}", pos.align_h));
}
switch (pos.align_v) {
case Alignment::START:
break;
case Alignment::END:
y -= rect.h;
break;
case Alignment::CENTER:
y -= rect.h / 2;
break;
default:
throw std::runtime_error(fmt::format("Unknown alignment: {}", pos.align_v));
}
rect.x = x;
rect.y = y;
}
@ -217,6 +245,15 @@ void ListWidget::select_prev() {
}
}
void ListWidget::select(size_t n) {
if (n > elements.size()) {
throw std::runtime_error(fmt::format(
"Tried to select element {}, but there are only {} elements!", n,
elements.size()));
}
selection = n;
}
size_t ListWidget::get_selection() { return selection; }
void ListWidget::place_element(Widget* element, int index) {
@ -232,11 +269,11 @@ void ListWidget::place_element(Widget* element, int index) {
break;
default:
throw std::runtime_error(
fmt::format("Unknown alignment: {}", (int)pos.align));
fmt::format("Unknown alignment: {}", element_alignment));
}
// Additional pixel for border
int y = rect.y + index * (element_height + 1);
element->set_position(x, y);
element->set_alignment(element_alignment);
element->set_alignment(element_alignment, Alignment::TOP);
element->set_height(element_height);
}