From 0cd0ce6f3ece0dfc5ac77c25e8338d88ce6b57d3 Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 28 May 2022 02:04:10 +0200 Subject: [PATCH] Implement AMI, more communication with STM etc It's late and I forgot to commit --- CMakeLists.txt | 3 +- include/AMI.h | 27 ++++++++++++++++ include/App.h | 13 ++++++++ include/AppState.h | 25 +++++++++++++-- include/MissionSelect.h | 4 +-- include/View.h | 4 +-- include/widgets.h | 20 ++++++++++-- src/AMI.cpp | 64 +++++++++++++++++++++++++++++++++++++ src/App.cpp | 71 ++++++++++++++++++++++++++--------------- src/AppState.cpp | 48 +++++++++++++++++++++++++--- src/MissionSelect.cpp | 31 ++++-------------- src/widgets.cpp | 57 +++++++++++++++++++++++++++------ 12 files changed, 293 insertions(+), 74 deletions(-) create mode 100644 include/AMI.h create mode 100644 src/AMI.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b1f00e8..abd3cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/include/AMI.h b/include/AMI.h new file mode 100644 index 0000000..f4bdbdb --- /dev/null +++ b/include/AMI.h @@ -0,0 +1,27 @@ +#pragma once + +#include "AppState.h" +#include "View.h" +#include "widgets.h" + +#include +#include + +#include +#include +#include + +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 header; + std::unordered_map> missions; +}; \ No newline at end of file diff --git a/include/App.h b/include/App.h index f15a2d7..685cf8f 100644 --- a/include/App.h +++ b/include/App.h @@ -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 state; std::unique_ptr mission_select; + std::unique_ptr ami; bool running; SDL_Window* window; SDL_Renderer* renderer; + +#ifndef NDEBUG + KeyboardHandler keyboard_handler; +#endif // !NDEBUG }; \ No newline at end of file diff --git a/include/AppState.h b/include/AppState.h index 0598dd4..fea7e40 100644 --- a/include/AppState.h +++ b/include/AppState.h @@ -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 i2c_dev_file; AppView view; -}; \ No newline at end of file + Mission mission; + +#ifndef NDEBUG + friend class KeyboardHandler; +#endif // !NDEBUG +}; + +extern AppState app_state; \ No newline at end of file diff --git a/include/MissionSelect.h b/include/MissionSelect.h index 616d315..c243319 100644 --- a/include/MissionSelect.h +++ b/include/MissionSelect.h @@ -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& events) override; + void draw(const AppState& state) override; private: TTF_Font* avenir; diff --git a/include/View.h b/include/View.h index ae8ac98..22db698 100644 --- a/include/View.h +++ b/include/View.h @@ -1,5 +1,6 @@ #pragma once +#include "AppState.h" #include "events.h" #include @@ -11,8 +12,7 @@ public: View(SDL_Renderer* renderer); virtual ~View(); - virtual void draw() = 0; - virtual void handle_events(std::queue& events) = 0; + virtual void draw(const AppState& state) = 0; protected: SDL_Renderer* renderer; diff --git a/include/widgets.h b/include/widgets.h index f1d7fb0..cd48cf2 100644 --- a/include/widgets.h +++ b/include/widgets.h @@ -3,17 +3,30 @@ #include #include +#include + #include +#include #include #include -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: diff --git a/src/AMI.cpp b/src/AMI.cpp new file mode 100644 index 0000000..97e3b03 --- /dev/null +++ b/src/AMI.cpp @@ -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 + +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(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(renderer, font_large, + "NO MISSION SELECTED")); + missions.emplace( + Mission::ACCELERATION, + std::make_unique(renderer, font_large, "ACCELERATION")); + missions.emplace(Mission::SKIDPAD, std::make_unique( + renderer, font_large, "SKIDPAD")); + missions.emplace(Mission::AUTOCROSS, std::make_unique( + renderer, font_large, "AUTOCROSS")); + missions.emplace( + Mission::TRACKDRIVE, + std::make_unique(renderer, font_large, "TRACKDRIVE")); + missions.emplace(Mission::EBS_TEST, std::make_unique( + renderer, font_large, "EBS TEST")); + missions.emplace( + Mission::INSPECTION, + std::make_unique(renderer, font_large, "INSPECTION")); + missions.emplace( + Mission::MANUAL, + std::make_unique(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(); +} \ No newline at end of file diff --git a/src/App.cpp b/src/App.cpp index be714c5..0c8678d 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -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(renderer); + ami = std::make_unique(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(static_cast(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(static_cast(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 + } } \ No newline at end of file diff --git a/src/AppState.cpp b/src/AppState.cpp index f11a10f..8002b0d 100644 --- a/src/AppState.cpp +++ b/src/AppState.cpp @@ -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(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(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; } \ No newline at end of file +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(data[1]); + spdlog::info("Mission after poll: {}", mission); +} \ No newline at end of file diff --git a/src/MissionSelect.cpp b/src/MissionSelect.cpp index c379707..7e1601e 100644 --- a/src/MissionSelect.cpp +++ b/src/MissionSelect.cpp @@ -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(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(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(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& 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)); - } - } -} \ No newline at end of file diff --git a/src/widgets.cpp b/src/widgets.cpp index 5cb5bd6..21418d0 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -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(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); }