mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-04 23:45:26 +00:00
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
This commit is contained in:
parent
5a0066963e
commit
dcaa02c1e1
@ -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
|
||||
|
@ -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<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
|
||||
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
|
||||
{"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<ExternalModuleView>(); }},
|
||||
});
|
||||
|
||||
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
|
||||
|
122
firmware/application/apps/ui_external_module_view.cpp
Normal file
122
firmware/application/apps/ui_external_module_view.cpp
Normal file
@ -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 <optional>
|
||||
|
||||
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
|
94
firmware/application/apps/ui_external_module_view.hpp
Normal file
94
firmware/application/apps/ui_external_module_view.hpp
Normal file
@ -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 <functional>
|
||||
#include <utility>
|
||||
|
||||
#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
|
@ -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<uint8_t[]> 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
|
||||
|
@ -31,28 +31,34 @@ namespace ui {
|
||||
|
||||
class StandaloneView : public View {
|
||||
public:
|
||||
StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> 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<uint8_t[]> _app_image;
|
||||
uint8_t* _app_image;
|
||||
standalone_application_information_t* get_application_information() const {
|
||||
return reinterpret_cast<standalone_application_information_t*>(_app_image.get());
|
||||
return reinterpret_cast<standalone_application_information_t*>(_app_image);
|
||||
}
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_sample{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
|
@ -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<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
|
||||
@ -84,6 +87,59 @@ namespace ui {
|
||||
|
||||
std::vector<GridItem> 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<char*>(&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<uint8_t*>(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<uint8_t[]>(app.size());
|
||||
// TODO: move this to m4 memory space
|
||||
auto app_image = reinterpret_cast<uint8_t*>(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<uint32_t*>(&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<StandaloneView>(std::move(app_image));
|
||||
nav.push<StandaloneView>(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<uint32_t*>(&app_image[file_read_index * 4]);
|
||||
|
||||
if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
|
||||
*ptr = *ptr - 0xADB10000 + (uint32_t)app_image;
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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<void(AppInfoConsole&)> callback);
|
||||
|
||||
private:
|
||||
|
83
firmware/common/i2cdev_ppmod.cpp
Normal file
83
firmware/common/i2cdev_ppmod.cpp
Normal file
@ -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 <optional>
|
||||
|
||||
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::device_info> 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::standalone_app_info> 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<uint8_t> 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<uint8_t> 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
|
73
firmware/common/i2cdev_ppmod.hpp
Normal file
73
firmware/common/i2cdev_ppmod.hpp
Normal file
@ -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 <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#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<device_info> readDeviceInfo();
|
||||
std::optional<standalone_app_info> getStandaloneAppInfo(uint32_t index);
|
||||
std::vector<uint8_t> downloadStandaloneApp(uint32_t index, size_t offset);
|
||||
};
|
||||
|
||||
} /* namespace i2cdev */
|
@ -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
|
||||
|
||||
|
@ -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<I2cDev_PPmod>();
|
||||
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;
|
||||
|
@ -26,9 +26,12 @@
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
|
||||
#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__*/
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user