From dcaa02c1e170043f4122acf3258541131cc6cd5b Mon Sep 17 00:00:00 2001 From: Bernd Herzog Date: Mon, 14 Oct 2024 13:07:41 +0200 Subject: [PATCH] External module api (#2290) * added external module debug view * integrated module driver into i2c subsystem * implemented i2c app loader * added view mirror api * fixed build * added i2c to module api * implemented standalone app touch event * implemented focus management * reverted formating change * refactoring * refactoring * implemented events * fixed memory usage --- firmware/application/CMakeLists.txt | 1 + firmware/application/apps/ui_debug.cpp | 2 + .../apps/ui_external_module_view.cpp | 122 ++++++++++++++++ .../apps/ui_external_module_view.hpp | 94 +++++++++++++ .../application/apps/ui_standalone_view.cpp | 132 +++++++++++++++++- .../application/apps/ui_standalone_view.hpp | 26 ++-- .../ui_external_items_menu_loader.cpp | 78 ++++++++++- .../ui_external_items_menu_loader.hpp | 1 + firmware/common/i2cdev_ppmod.cpp | 83 +++++++++++ firmware/common/i2cdev_ppmod.hpp | 73 ++++++++++ firmware/common/i2cdevlist.hpp | 3 + firmware/common/i2cdevmanager.cpp | 6 + firmware/common/standalone_app.hpp | 32 ++++- firmware/common/ui_focus.cpp | 17 ++- firmware/common/ui_focus.hpp | 4 + firmware/common/ui_text.hpp | 4 + firmware/common/ui_widget.cpp | 6 + firmware/common/ui_widget.hpp | 7 +- 18 files changed, 667 insertions(+), 24 deletions(-) create mode 100644 firmware/application/apps/ui_external_module_view.cpp create mode 100644 firmware/application/apps/ui_external_module_view.hpp create mode 100644 firmware/common/i2cdev_ppmod.cpp create mode 100644 firmware/common/i2cdev_ppmod.hpp diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 7f39c5815..bc21e8be0 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -296,6 +296,7 @@ set(CPPSRC apps/ui_debug_max17055.cpp apps/ui_dfu_menu.cpp apps/ui_encoders.cpp + apps/ui_external_module_view.cpp apps/ui_fileman.cpp apps/ui_flash_utility.cpp apps/ui_freqman.cpp diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index 9587dfc0c..e3a7f48ec 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -37,6 +37,7 @@ #include "ui_painter.hpp" #include "ui_external_items_menu_loader.hpp" #include "ui_debug_max17055.hpp" +#include "ui_external_module_view.hpp" #include "portapack.hpp" #include "portapack_persistent_memory.hpp" @@ -514,6 +515,7 @@ void DebugMenuView::on_populate() { {"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push(); }}, {"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push(); }}, {"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push(); }}, + {"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push(); }}, }); if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) { diff --git a/firmware/application/apps/ui_external_module_view.cpp b/firmware/application/apps/ui_external_module_view.cpp new file mode 100644 index 000000000..872f0e49e --- /dev/null +++ b/firmware/application/apps/ui_external_module_view.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_external_module_view.hpp" +#include "portapack.hpp" +#include "ui_standalone_view.hpp" + +#include "i2cdevmanager.hpp" +#include "i2cdev_ppmod.hpp" + +#include + +namespace ui { + +void ExternalModuleView::focus() { + dummy.focus(); +} + +void ExternalModuleView::on_tick_second() { + i2cdev::I2CDevManager::manual_scan(); + + auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD); + + if (!dev) { + text_header.set("No module connected"); + text_name.set(""); + text_version.set(""); + text_number_apps.set(""); + text_app1_name.set(""); + text_app2_name.set(""); + text_app3_name.set(""); + text_app4_name.set(""); + text_app5_name.set(""); + return; + } + + auto device_info = dev->readDeviceInfo(); + + if (device_info.has_value() == false) { + text_header.set("No module connected"); + text_name.set(""); + text_version.set(""); + text_number_apps.set(""); + text_app1_name.set(""); + text_app2_name.set(""); + text_app3_name.set(""); + text_app4_name.set(""); + text_app5_name.set(""); + return; + } + + text_header.set("Module found"); + + std::string btnText = (std::string) "Module: " + device_info->module_name; + text_name.set(btnText); + text_version.set("Version: " + std::to_string(device_info->module_version)); + text_number_apps.set("No# Apps: " + std::to_string(device_info->application_count)); + + for (uint32_t i = 0; i < device_info->application_count && i < 5; i++) { + auto appInfo = dev->getStandaloneAppInfo(i); + if (appInfo.has_value() == false) { + continue; + } + + std::string btnText = (std::string) "App " + std::to_string(device_info->application_count) + ": " + (const char*)appInfo->app_name; + + switch (appInfo->menu_location) { + case app_location_t::UTILITIES: + btnText += " (Utilities)"; + break; + case app_location_t::RX: + btnText += " (RX)"; + break; + case app_location_t::TX: + btnText += " (TX)"; + break; + case app_location_t::DEBUG: + btnText += " (Debug)"; + break; + case app_location_t::HOME: + btnText += " (Home)"; + break; + } + + switch (i) { + case 0: + text_app1_name.set(btnText); + break; + case 1: + text_app2_name.set(btnText); + break; + case 2: + text_app3_name.set(btnText); + break; + case 3: + text_app4_name.set(btnText); + break; + case 4: + text_app5_name.set(btnText); + break; + } + } +} +} // namespace ui diff --git a/firmware/application/apps/ui_external_module_view.hpp b/firmware/application/apps/ui_external_module_view.hpp new file mode 100644 index 000000000..2bc14344f --- /dev/null +++ b/firmware/application/apps/ui_external_module_view.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_menu.hpp" +#include "ui_navigation.hpp" + +#include "rffc507x.hpp" +#include "portapack.hpp" +#include "memory_map.hpp" +#include "irq_controls.hpp" + +#include +#include + +#include "i2cdevmanager.hpp" +#include "i2cdev_ppmod.hpp" + +namespace ui { + +class ExternalModuleView : public View { + public: + ExternalModuleView(NavigationView& nav) + : nav_(nav) { + add_children({&text_header, + &text_name, + &text_version, + &text_number_apps, + &text_app1_name, + &text_app2_name, + &text_app3_name, + &text_app4_name, + &text_app5_name, + &dummy}); + + text_header.set("No module connected"); + + signal_token_tick_second = rtc_time::signal_tick_second += [this]() { + on_tick_second(); + }; + } + + ~ExternalModuleView() { + rtc_time::signal_tick_second -= signal_token_tick_second; + } + + std::string title() const override { return "Ext Module"; }; + void focus() override; + + private: + NavigationView& nav_; + Text text_header{{16, 16, 208, 16}}; + Text text_name{{24, 32, 200, 16}}; + Text text_version{{24, 48, 200, 16}}; + Text text_number_apps{{24, 64, 200, 16}}; + + Text text_app1_name{{24, 96, 200, 16}}; + Text text_app2_name{{24, 112, 200, 16}}; + Text text_app3_name{{24, 128, 200, 16}}; + Text text_app4_name{{24, 144, 200, 16}}; + Text text_app5_name{{24, 160, 200, 16}}; + + Button dummy{ + {240, 0, 0, 0}, + ""}; + + SignalToken signal_token_tick_second{}; + + void on_tick_second(); +}; + +} // namespace ui diff --git a/firmware/application/apps/ui_standalone_view.cpp b/firmware/application/apps/ui_standalone_view.cpp index cbab847cf..f14136016 100644 --- a/firmware/application/apps/ui_standalone_view.cpp +++ b/firmware/application/apps/ui_standalone_view.cpp @@ -22,6 +22,12 @@ #include "ui_standalone_view.hpp" #include "irq_controls.hpp" +#include "i2cdevmanager.hpp" +#include "i2cdev_ppmod.hpp" + +#include "ui_font_fixed_5x8.hpp" +#include "ui_font_fixed_8x16.hpp" + namespace ui { void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) { @@ -34,6 +40,16 @@ void fill_rectangle(int x, int y, int width, int height, uint16_t color) { painter.fill_rectangle({x, y, width, height}, ui::Color(color)); } +void fill_rectangle_unrolled8(int x, int y, int width, int height, uint16_t color) { + ui::Painter painter; + painter.fill_rectangle_unrolled8({x, y, width, height}, ui::Color(color)); +} + +void draw_bitmap(int x, int y, int width, int height, const uint8_t* pixels, uint16_t foreground, uint16_t background) { + ui::Painter painter; + painter.draw_bitmap({x, y}, {{width, height}, pixels}, ui::Color(foreground), ui::Color(background)); +} + void* alloc(size_t size) { void* p = chHeapAlloc(0x0, size); if (p == nullptr) @@ -45,6 +61,45 @@ uint64_t get_switches_state_ulong() { return get_switches_state().to_ulong(); } +ui::Coord scroll_area_y(const ui::Coord y) { + return portapack::display.scroll_area_y(y); +} + +void scroll_set_area(const ui::Coord top_y, const ui::Coord bottom_y) { + portapack::display.scroll_set_area(top_y, bottom_y); +} + +void scroll_disable() { + portapack::display.scroll_disable(); +} + +ui::Coord scroll_set_position(const ui::Coord position) { + return portapack::display.scroll_set_position(position); +} + +ui::Coord scroll(const int32_t delta) { + return portapack::display.scroll(delta); +} + +bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) { + auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD); + if (!dev) { + return false; + } + + if (data_len == 0 || data == nullptr) + return dev->i2c_write(nullptr, 0, cmd, cmd_len); + + return dev->i2c_read(cmd, cmd_len, data, data_len); +} + +StandaloneView* standaloneView = nullptr; + +void set_dirty() { + if (standaloneView != nullptr) + standaloneView->set_dirty(); +} + standalone_application_api_t api = { /* .malloc = */ &alloc, /* .calloc = */ &calloc, @@ -54,20 +109,77 @@ standalone_application_api_t api = { /* .fill_rectangle = */ &fill_rectangle, /* .swizzled_switches = */ &swizzled_switches, /* .get_switches_state = */ &get_switches_state_ulong, + /* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(), + /* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(), + /* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8, + /* .draw_bitmap = */ &draw_bitmap, + /* .scroll_area_y = */ &scroll_area_y, + /* .scroll_set_area = */ &scroll_set_area, + /* .scroll_disable = */ &scroll_disable, + /* .scroll_set_position = */ &scroll_set_position, + /* .scroll = */ &scroll, + /* .i2c_read = */ &i2c_read, + /* .panic = */ &chDbgPanic, + /* .set_dirty = */ &set_dirty, }; -StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr app_image) - : nav_(nav), _app_image(std::move(app_image)) { - get_application_information()->initialize(api); - add_children({&dummy}); +StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image) + : nav_(nav), _app_image(app_image) { + if (_app_image == nullptr) { + chDbgPanic("Invalid application image"); + } + + set_focusable(true); + + standaloneView = this; } void StandaloneView::focus() { - dummy.focus(); + View::focus(); } void StandaloneView::paint(Painter& painter) { (void)painter; + + if (initialized && + get_application_information()->header_version > 1) { + get_application_information()->PaintViewMirror(); + } +} + +void StandaloneView::on_focus() { + if (get_application_information()->header_version > 1) { + get_application_information()->OnFocus(); + } +} + +bool StandaloneView::on_key(const KeyEvent key) { + if (get_application_information()->header_version > 1) { + return get_application_information()->OnKeyEvent((uint8_t)key); + } + + return false; +} + +bool StandaloneView::on_encoder(const EncoderEvent event) { + if (get_application_information()->header_version > 1) { + return get_application_information()->OnEncoder((int32_t)event); + } + return false; +} + +bool StandaloneView::on_touch(const TouchEvent event) { + if (get_application_information()->header_version > 1) { + get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type); + } + return true; +} + +bool StandaloneView::on_keyboard(const KeyboardEvent event) { + if (get_application_information()->header_version > 1) { + return get_application_information()->OnKeyboad((uint8_t)event); + } + return false; } void StandaloneView::frame_sync() { @@ -79,4 +191,14 @@ void StandaloneView::frame_sync() { } } +void StandaloneView::on_after_attach() { + context().focus_manager().setMirror(this); + get_application_information()->initialize(api); +} + +void StandaloneView::on_before_detach() { + get_application_information()->shutdown(); + context().focus_manager().clearMirror(); +} + } // namespace ui diff --git a/firmware/application/apps/ui_standalone_view.hpp b/firmware/application/apps/ui_standalone_view.hpp index 012d7f07f..97bda0d14 100644 --- a/firmware/application/apps/ui_standalone_view.hpp +++ b/firmware/application/apps/ui_standalone_view.hpp @@ -31,28 +31,34 @@ namespace ui { class StandaloneView : public View { public: - StandaloneView(NavigationView& nav, std::unique_ptr app_image); - virtual ~StandaloneView() override { get_application_information()->shutdown(); } - - void focus() override; + StandaloneView(NavigationView& nav, uint8_t* app_image); + virtual ~StandaloneView() override { + } std::string title() const override { return (const char*)get_application_information()->app_name; }; + void focus() override; void paint(Painter& painter) override; + + void on_focus() override; + bool on_key(const KeyEvent key) override; + bool on_encoder(const EncoderEvent event) override; + bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; + + void on_after_attach() override; + void on_before_detach() override; + void frame_sync(); private: bool initialized = false; NavigationView& nav_; - std::unique_ptr _app_image; + uint8_t* _app_image; standalone_application_information_t* get_application_information() const { - return reinterpret_cast(_app_image.get()); + return reinterpret_cast(_app_image); } - Button dummy{ - {240, 0, 0, 0}, - ""}; - MessageHandlerRegistration message_handler_sample{ Message::ID::DisplayFrameSync, [this](const Message* const) { diff --git a/firmware/application/ui_external_items_menu_loader.cpp b/firmware/application/ui_external_items_menu_loader.cpp index b107bc1c0..7090e8679 100644 --- a/firmware/application/ui_external_items_menu_loader.cpp +++ b/firmware/application/ui_external_items_menu_loader.cpp @@ -4,6 +4,9 @@ #include "file_path.hpp" #include "ui_standalone_view.hpp" +#include "i2cdevmanager.hpp" +#include "i2cdev_ppmod.hpp" + namespace ui { /* static */ std::vector> ExternalItemsMenuLoader::bitmaps; @@ -84,6 +87,59 @@ namespace ui { std::vector external_apps; + auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD); + + if (dev) { + auto device_info = dev->readDeviceInfo(); + + if (device_info.has_value()) { + for (uint32_t i = 0; i < device_info->application_count; i++) { + auto appInfo = dev->getStandaloneAppInfo(i); + if (appInfo.has_value() == false) { + continue; + } + + if (appInfo->menu_location != app_location) { + continue; + } + + if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION) + continue; + + GridItem gridItem = {}; + gridItem.text = reinterpret_cast(&appInfo->app_name[0]); + + gridItem.color = Color((uint16_t)appInfo->icon_color); + + auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data}; + gridItem.bitmap = dyn_bmp.bitmap(); + bitmaps.push_back(std::move(dyn_bmp)); + + gridItem.on_select = [&nav, appInfo, i]() { + auto dev2 = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD); + if (dev2) { + auto app_image = reinterpret_cast(portapack::memory::map::m4_code.end() - appInfo->binary_size); + for (size_t j = 0; j < appInfo->binary_size; j += 128) { + auto segment = dev2->downloadStandaloneApp(i, j); + if (segment.size() != 128) { + continue; + } + + std::copy(segment.begin(), segment.end(), app_image + j); + } + + if (!run_module_app(nav, app_image, appInfo->binary_size)) { + nav.display_modal("Error", "Unable to run downloaded app."); + } + } else + nav.display_modal("Error", "Unable to download app."); + }; + + external_apps.push_back(gridItem); + } + } + } + if (sd_card::status() != sd_card::Status::Mounted) return external_apps; @@ -259,6 +315,7 @@ namespace ui { return true; } +// TODO: implement baseband image support /* static */ bool ExternalItemsMenuLoader::run_standalone_app(ui::NavigationView& nav, std::filesystem::path filePath) { File app; @@ -266,7 +323,8 @@ namespace ui { if (openError) return false; - auto app_image = std::make_unique(app.size()); + // TODO: move this to m4 memory space + auto app_image = reinterpret_cast(portapack::memory::map::m4_code.end() - app.size()); // read file in 512 byte chunks for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) { @@ -286,11 +344,25 @@ namespace ui { uint32_t* ptr = reinterpret_cast(&app_image[file_read_index * 4]); if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) { - *ptr = *ptr - 0xADB10000 + (uint32_t)app_image.get(); + *ptr = *ptr - 0xADB10000 + (uint32_t)app_image; } } - nav.push(std::move(app_image)); + nav.push(app_image); + return true; +} + +// TODO: implement baseband image support +/* static */ bool ExternalItemsMenuLoader::run_module_app(ui::NavigationView& nav, uint8_t* app_image, size_t app_size) { + for (size_t file_read_index = 0; file_read_index < app_size / 4; file_read_index++) { + uint32_t* ptr = reinterpret_cast(&app_image[file_read_index * 4]); + + if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) { + *ptr = *ptr - 0xADB10000 + (uint32_t)app_image; + } + } + + nav.push(app_image); return true; } diff --git a/firmware/application/ui_external_items_menu_loader.hpp b/firmware/application/ui_external_items_menu_loader.hpp index 74745d176..d9b932971 100644 --- a/firmware/application/ui_external_items_menu_loader.hpp +++ b/firmware/application/ui_external_items_menu_loader.hpp @@ -59,6 +59,7 @@ class ExternalItemsMenuLoader { ExternalItemsMenuLoader() = delete; static bool run_external_app(ui::NavigationView&, std::filesystem::path); static bool run_standalone_app(ui::NavigationView&, std::filesystem::path); + static bool run_module_app(ui::NavigationView&, uint8_t*, size_t); static void load_all_external_items_callback(std::function callback); private: diff --git a/firmware/common/i2cdev_ppmod.cpp b/firmware/common/i2cdev_ppmod.cpp new file mode 100644 index 000000000..c0739df52 --- /dev/null +++ b/firmware/common/i2cdev_ppmod.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "i2cdev_ppmod.hpp" +#include "portapack.hpp" + +#include + +namespace i2cdev { + +bool I2cDev_PPmod::init(uint8_t addr_) { + if (addr_ != I2CDEV_PPMOD_ADDR_1) return false; + addr = addr_; + model = I2CDECMDL_PPMOD; + + return true; +} + +void I2cDev_PPmod::update() { +} + +std::optional I2cDev_PPmod::readDeviceInfo() { + Command cmd = Command::COMMAND_INFO; + I2cDev_PPmod::device_info info; + + bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&info, sizeof(I2cDev_PPmod::device_info)); + if (success == false) { + return std::nullopt; + } + + return info; +} + +std::optional I2cDev_PPmod::getStandaloneAppInfo(uint32_t index) { + Command cmd = Command::COMMAND_APP_INFO; + uint32_t data = (uint32_t)cmd + (index << 16); + I2cDev_PPmod::standalone_app_info info; + + bool success = i2c_read((uint8_t*)&data, 4, (uint8_t*)&info, sizeof(I2cDev_PPmod::standalone_app_info)); + if (success == false) { + return std::nullopt; + } + + return info; +} + +constexpr size_t transfer_block_size = 128; + +std::vector I2cDev_PPmod::downloadStandaloneApp(uint32_t index, size_t offset) { + if (offset % transfer_block_size != 0) { + return {}; + } + + uint16_t data[3] = {(uint16_t)Command::COMMAND_APP_TRANSFER, index, offset / transfer_block_size}; + + std::vector ret(transfer_block_size); + bool success = i2c_read((uint8_t*)&data, sizeof(data), (uint8_t*)ret.data(), transfer_block_size); + if (success == false) { + return {}; + } + + return ret; +} + +} // namespace i2cdev diff --git a/firmware/common/i2cdev_ppmod.hpp b/firmware/common/i2cdev_ppmod.hpp new file mode 100644 index 000000000..bbbba96c3 --- /dev/null +++ b/firmware/common/i2cdev_ppmod.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include + +#include "standalone_app.hpp" +#include "i2cdevmanager.hpp" + +namespace i2cdev { + +class I2cDev_PPmod : public I2cDev { + public: + enum class Command : uint16_t { + COMMAND_NONE = 0, + + // will respond with device_info + COMMAND_INFO = 0x18F0, + + // will respond with info of application + COMMAND_APP_INFO = 0xA90B, + + // will respond with application data + COMMAND_APP_TRANSFER = 0x4183, + }; + + typedef struct { + uint32_t api_version; + uint32_t module_version; + char module_name[20]; + uint32_t application_count; + } device_info; + + typedef struct { + uint32_t header_version; + uint8_t app_name[16]; + uint8_t bitmap_data[32]; + uint32_t icon_color; + app_location_t menu_location; + uint32_t binary_size; + } standalone_app_info; + + bool init(uint8_t addr_) override; + void update() override; + + std::optional readDeviceInfo(); + std::optional getStandaloneAppInfo(uint32_t index); + std::vector downloadStandaloneApp(uint32_t index, size_t offset); +}; + +} /* namespace i2cdev */ diff --git a/firmware/common/i2cdevlist.hpp b/firmware/common/i2cdevlist.hpp index 4d6d43a35..34ad71269 100644 --- a/firmware/common/i2cdevlist.hpp +++ b/firmware/common/i2cdevlist.hpp @@ -35,6 +35,7 @@ enum I2C_DEVMDL { I2CDEVMDL_BMP280, I2CDEVMDL_BME280, I2CDECMDL_BH1750, + I2CDECMDL_PPMOD, }; #define I2CDEV_BMX280_ADDR_1 0x76 @@ -47,6 +48,8 @@ enum I2C_DEVMDL { #define I2CDEV_BH1750_ADDR_1 0x23 +#define I2CDEV_PPMOD_ADDR_1 0x51 + // this will be the update interval for battery management ic's: #define BATTERY_WIDGET_REFRESH_INTERVAL 10 diff --git a/firmware/common/i2cdevmanager.cpp b/firmware/common/i2cdevmanager.cpp index 977d28849..3a83e14f3 100644 --- a/firmware/common/i2cdevmanager.cpp +++ b/firmware/common/i2cdevmanager.cpp @@ -31,6 +31,7 @@ #include "i2cdev_max17055.hpp" #include "i2cdev_ads1110.hpp" #include "i2cdev_bh1750.hpp" +#include "i2cdev_ppmod.hpp" namespace i2cdev { @@ -86,6 +87,11 @@ bool I2CDevManager::found(uint8_t addr) { if (!item.dev->init(addr)) item.dev = nullptr; } + if (!item.dev && (addr == I2CDEV_PPMOD_ADDR_1)) { + item.dev = std::make_unique(); + if (!item.dev->init(addr)) item.dev = nullptr; + } + // if can't find any driver, add it too with empty, so we won't try to init it again and again devlist.push_back(std::move(item)); return true; diff --git a/firmware/common/standalone_app.hpp b/firmware/common/standalone_app.hpp index 5a34f8b71..b2b455b28 100644 --- a/firmware/common/standalone_app.hpp +++ b/firmware/common/standalone_app.hpp @@ -26,9 +26,12 @@ #include #include -#define CURRENT_STANDALONE_APPLICATION_API_VERSION 1 +#include "ui.hpp" + +#define CURRENT_STANDALONE_APPLICATION_API_VERSION 2 struct standalone_application_api_t { + // Version 1 void* (*malloc)(size_t size); void* (*calloc)(size_t num, size_t size); void* (*realloc)(void* p, size_t size); @@ -38,6 +41,26 @@ struct standalone_application_api_t { uint8_t (*swizzled_switches)(); uint64_t (*get_switches_state)(); + // Version 2 + const uint8_t* fixed_5x8_glyph_data; + const uint8_t* fixed_8x16_glyph_data; + + void (*fill_rectangle_unrolled8)(int x, int y, int width, int height, uint16_t color); + void (*draw_bitmap)(int x, int y, int width, int height, const uint8_t* pixels, uint16_t foreground, uint16_t background); + + ui::Coord (*scroll_area_y)(const ui::Coord y); + void (*scroll_set_area)(const ui::Coord top_y, const ui::Coord bottom_y); + void (*scroll_disable)(); + ui::Coord (*scroll_set_position)(const ui::Coord position); + ui::Coord (*scroll)(const int32_t delta); + + bool (*i2c_read)(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len); + void (*panic)(const char* msg); + void (*set_dirty)(); + + // TODO: add filesystem access functions + // TODO: add baseband access functions + // HOW TO extend this interface: // to keep everything backward compatible: add new fields at the end // and increment CURRENT_STANDALONE_APPLICATION_API_VERSION @@ -69,6 +92,13 @@ struct standalone_application_information_t { /// @brief gets called once at application shutdown void (*shutdown)(); + + void (*PaintViewMirror)(); + void (*OnTouchEvent)(int x, int y, uint32_t type); + void (*OnFocus)(); + bool (*OnKeyEvent)(uint8_t key); + bool (*OnEncoder)(int32_t delta); + bool (*OnKeyboad)(uint8_t key); }; #endif /*__UI_STANDALONE_APP_H__*/ diff --git a/firmware/common/ui_focus.cpp b/firmware/common/ui_focus.cpp index c8015a700..dc92cec17 100644 --- a/firmware/common/ui_focus.cpp +++ b/firmware/common/ui_focus.cpp @@ -38,6 +38,14 @@ Widget* FocusManager::focus_widget() const { return focus_widget_; } +void FocusManager::setMirror(Widget* const mirror_widget) { + mirror_widget_ = mirror_widget; +} + +void FocusManager::clearMirror() { + mirror_widget_ = nullptr; +} + void FocusManager::set_focus_widget(Widget* const new_focus_widget) { // Widget already has focus. if (new_focus_widget == focus_widget()) { @@ -153,9 +161,12 @@ static int32_t rect_distances( } } -void FocusManager::update( - Widget* const top_widget, - const KeyEvent event) { +void FocusManager::update(Widget* const top_widget, const KeyEvent event) { + if (mirror_widget_) { + if (mirror_widget_->on_key(event)) + return; + } + if (focus_widget()) { const auto focus_screen_rect = focus_widget()->screen_rect(); diff --git a/firmware/common/ui_focus.hpp b/firmware/common/ui_focus.hpp index 2f9c45650..847e5c244 100644 --- a/firmware/common/ui_focus.hpp +++ b/firmware/common/ui_focus.hpp @@ -36,8 +36,12 @@ class FocusManager { void update(Widget* const top_widget, const KeyEvent event); // void update(Widget* const top_widget, const TouchEvent event); + void setMirror(Widget* const mirror_widget); + void clearMirror(); + private: Widget* focus_widget_{nullptr}; + Widget* mirror_widget_{nullptr}; }; } /* namespace ui */ diff --git a/firmware/common/ui_text.hpp b/firmware/common/ui_text.hpp index 123da5360..578cbf61a 100644 --- a/firmware/common/ui_text.hpp +++ b/firmware/common/ui_text.hpp @@ -94,6 +94,10 @@ class Font { Size size_of(const std::string s) const; + const uint8_t* get_data() const { + return data; + } + private: const Dim w; const Dim h; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 263868f9d..11465958d 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -94,8 +94,14 @@ void Widget::set_parent(Widget* const widget) { visible(false); } + if (widget == nullptr) + on_before_detach(); + parent_ = widget; + if (widget != nullptr) + on_after_attach(); + set_dirty(); } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 43a80e019..e4c775247 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -84,6 +84,9 @@ class Widget { Widget* parent() const; void set_parent(Widget* const widget); + virtual void on_after_attach() { return; }; + virtual void on_before_detach() { return; }; + bool hidden() const { return flags.hidden; } void hidden(bool hide); @@ -97,8 +100,8 @@ class Widget { virtual void paint(Painter& painter) = 0; - virtual void on_show(){}; - virtual void on_hide(){}; + virtual void on_show() { return; }; + virtual void on_hide() { return; }; virtual bool on_key(const KeyEvent event); virtual bool on_encoder(const EncoderEvent event);