mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 11:02:58 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dfbf666d0 | |||
| 245a36459f | |||
| 590f19062b | |||
| f38ef4ebc8 | |||
| 900d2f2482 | |||
| 5aff5ac277 | |||
| cf4d245964 | |||
| c7f7173958 | |||
| 02c0f67a8d | |||
| 2bacbc80a8 | |||
| ab986a0378 | |||
| c9f310f548 |
@@ -332,7 +332,6 @@ set(CPPSRC
|
||||
${CPLD_H4M_DATA_CPP}
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
)
|
||||
|
||||
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||
|
||||
@@ -322,10 +322,10 @@ class BLERxView : public View {
|
||||
{UI_POS_X(21), UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(6), 4}};
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Channel channel{
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(6), 4}};
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Labels label_sort{
|
||||
{{UI_POS_X(0), UI_POS_Y(1)}, "Sort:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path cu8_ext{u".CU8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path cxx_ext{u".C*"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
@@ -88,8 +89,11 @@ fs::path get_partner_file(fs::path path) {
|
||||
path.replace_extension(txt_ext);
|
||||
else if (path_iequal(ext, txt_ext)) {
|
||||
path.replace_extension(c8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(cu8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c16_ext);
|
||||
|
||||
} else
|
||||
return {};
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class FileManBaseView : public View {
|
||||
{u".PNG", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".BMP", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".CU8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
|
||||
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
|
||||
|
||||
@@ -172,6 +172,7 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
|
||||
|
||||
void IQTrimView::profile_capture() {
|
||||
power_buckets_.clear();
|
||||
power_buckets_.resize(screen_width);
|
||||
iq::PowerBuckets buckets{
|
||||
.p = power_buckets_.data(),
|
||||
.size = power_buckets_.size()};
|
||||
|
||||
@@ -37,8 +37,20 @@
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "hackrf_gpio.hpp"
|
||||
using namespace hackrf::one;
|
||||
|
||||
#include "irq_rtc.hpp"
|
||||
|
||||
#include "i2c_lld.h"
|
||||
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
|
||||
#include "lpc43xx.inc"
|
||||
#include "nvic.h"
|
||||
#include "lpc43xx_m0.h"
|
||||
#include "rffc507x_spi.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -134,7 +146,186 @@ void EventDispatcher::set_display_sleep(const bool sleep) {
|
||||
// Let frame sync handler turn on backlight after repaint.
|
||||
}
|
||||
EventDispatcher::display_sleep = sleep;
|
||||
};
|
||||
}
|
||||
|
||||
void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
||||
bool detect = false;
|
||||
uint8_t valid_mask = 0;
|
||||
uint8_t percent = 0;
|
||||
uint16_t voltage = 0;
|
||||
int32_t current = 0;
|
||||
|
||||
constexpr I2CConfig i2c_config_12mhz{
|
||||
.high_count = 15,
|
||||
.low_count = 15,
|
||||
};
|
||||
|
||||
if (sleep) {
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
if (dev) dev->send_poweroff_command();
|
||||
|
||||
rffc507x::spi::SPI().power_down();
|
||||
portapack::shutdown(false, true);
|
||||
|
||||
// Unmount SD card and stop driver
|
||||
f_mount(nullptr, reinterpret_cast<const TCHAR*>(_T("")), 0);
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
|
||||
// Signal application shutdown
|
||||
ShutdownMessage shutdown_message;
|
||||
shared_memory.application_queue.push(shutdown_message);
|
||||
shared_memory.baseband_message = nullptr;
|
||||
|
||||
// Disable core interrupts and system tick
|
||||
nvicDisableVector(DMA_IRQn);
|
||||
nvicDisableVector(M4CORE_IRQn);
|
||||
chSysDisable();
|
||||
systick_stop();
|
||||
|
||||
SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
|
||||
|
||||
#ifdef PRALINE
|
||||
// Power management and GPIO configuration for Praline hardware
|
||||
gpio_vaa_disable.set();
|
||||
gpio_1v2_enable.clear();
|
||||
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
||||
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
||||
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
||||
LPC_GPIO->DIR[3] &= ~((1 << 7) | (1 << 5));
|
||||
LPC_GPIO->DIR[5] &= ~(1 << 16);
|
||||
#else
|
||||
// Power management and GPIO configuration for legacy hardware
|
||||
gpio_og_vaa_disable.set();
|
||||
gpio_r9_vaa_disable.clear();
|
||||
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
||||
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
||||
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
||||
LPC_GPIO->DIR[3] &= ~((1 << 7) | (1 << 5));
|
||||
LPC_GPIO->DIR[5] &= ~(1 << 16);
|
||||
#endif
|
||||
|
||||
// Power down peripherals (CGU cleanup)
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 5); // USB0 Reset
|
||||
LPC_CGU->PLL0USB_CTRL.PD = 1;
|
||||
LPC_CGU->BASE_USB0_CLK.PD = 1;
|
||||
LPC_CREG->CREG0 |= (1 << 5);
|
||||
|
||||
LPC_CGU->BASE_USB1_CLK.PD = 1;
|
||||
LPC_CGU->BASE_UART0_CLK.PD = 1;
|
||||
LPC_CGU->BASE_UART1_CLK.PD = 1;
|
||||
LPC_CGU->BASE_UART2_CLK.PD = 1;
|
||||
LPC_CGU->BASE_UART3_CLK.PD = 1;
|
||||
LPC_CGU->BASE_SPI_CLK.PD = 1;
|
||||
LPC_CGU->BASE_PERIPH_CLK.PD = 1;
|
||||
LPC_CGU->BASE_SDIO_CLK.PD = 1;
|
||||
LPC_CGU->BASE_SSP0_CLK.PD = 1;
|
||||
LPC_CGU->BASE_SSP1_CLK.PD = 1;
|
||||
LPC_CGU->BASE_LCD_CLK.PD = 1;
|
||||
LPC_CGU->BASE_OUT_CLK.PD = 1;
|
||||
|
||||
(*(volatile uint32_t*)(&LPC_CGU->PLL0AUDIO_CTRL)) |= (1 << 0);
|
||||
|
||||
LPC_ADC0->CR &= ~(1 << 21);
|
||||
LPC_ADC1->CR &= ~(1 << 21);
|
||||
led_rx.off();
|
||||
led_usb.off();
|
||||
|
||||
rtc_wakeup_init();
|
||||
NVIC_EnableIRQ(I2C0_OR_I2C1_IRQn);
|
||||
|
||||
while (1) {
|
||||
// --- Battery Status Check (I2C) ---
|
||||
detect = battery::BatteryManagement::isDetected();
|
||||
if (detect) {
|
||||
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
|
||||
|
||||
bool is_full = (valid_mask == 31 && percent == 100 && current <= 10) ||
|
||||
(valid_mask == 1 && percent == 100);
|
||||
|
||||
if (is_full) {
|
||||
// Case 1: Battery full (All LEDs off)
|
||||
led_rx.off();
|
||||
led_tx.off();
|
||||
} else if ((voltage < 4150 && current < 10) || valid_mask == 0) {
|
||||
// Case 2: Not full but low current draw (<10mA) -> Charging error
|
||||
led_tx.on(); // LED indicates error/idle
|
||||
led_rx.off();
|
||||
} else {
|
||||
// Case 3: Actively charging
|
||||
led_rx.on(); // LED indicates charging
|
||||
led_tx.off();
|
||||
}
|
||||
} else {
|
||||
// Case 4: Battery IC not detected -> Error or H2 or older, so don't show that as an error.
|
||||
led_tx.on();
|
||||
led_rx.on();
|
||||
}
|
||||
|
||||
// Shut down I2C and power down the APB bus for sleep
|
||||
portapack::i2c0.stop();
|
||||
LPC_CGU->BASE_APB1_CLK.PD = 1;
|
||||
|
||||
// Save interrupt states before mass disable
|
||||
uint32_t saved_iser0 = NVIC->ISER[0];
|
||||
|
||||
// Disable and clear all pending interrupts
|
||||
NVIC->ICER[0] = 0xFFFFFFFF;
|
||||
|
||||
NVIC->ICPR[0] = 0xFFFFFFFF;
|
||||
|
||||
// Re-enable only necessary wakeup sources
|
||||
NVIC_EnableIRQ(RTC_IRQn);
|
||||
NVIC_EnableIRQ(EVENTROUTER_IRQn);
|
||||
|
||||
// Configure RTC wakeup interval
|
||||
if (valid_mask != 0 || detect) {
|
||||
rtc_wakeup(60);
|
||||
} else {
|
||||
rtc_wakeup(3);
|
||||
}
|
||||
|
||||
LPC_RTC->ILR = 3;
|
||||
LPC_EVENTROUTER->CLR_STAT = 0xFFFFFFFF;
|
||||
NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
NVIC_ClearPendingIRQ(EVENTROUTER_IRQn);
|
||||
|
||||
__disable_irq();
|
||||
|
||||
// Configure and enter Deep Sleep
|
||||
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
|
||||
|
||||
__DSB();
|
||||
__ISB();
|
||||
|
||||
// CPU enters sleep here
|
||||
__WFI();
|
||||
|
||||
// --- WAKEUP SEQUENCE ---
|
||||
__enable_irq();
|
||||
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
|
||||
|
||||
// Cleanup RTC and restore peripheral clocks
|
||||
LPC_RTC->AMR = 0xFF;
|
||||
LPC_RTC->ILR = 3;
|
||||
LPC_CGU->BASE_APB1_CLK.PD = 0;
|
||||
LPC_RGU->RESET_CTRL[1] = (1 << 16);
|
||||
|
||||
// Short delay for power stability (3V3 rail)
|
||||
for (volatile int d = 0; d < 10000; d++);
|
||||
|
||||
// Restart I2C controller
|
||||
LPC_CGU->BASE_APB1_CLK.PD = 0;
|
||||
portapack::i2c0.start(i2c_config_12mhz);
|
||||
|
||||
// Restore original interrupt enable states
|
||||
NVIC->ISER[0] = saved_iser0;
|
||||
|
||||
} // End of while(1) deep sleep loop
|
||||
} else {
|
||||
portapack::display.wake(true);
|
||||
}
|
||||
}
|
||||
|
||||
eventmask_t EventDispatcher::wait() {
|
||||
return chEvtWaitAny(ALL_EVENTS);
|
||||
|
||||
@@ -63,6 +63,8 @@ class EventDispatcher {
|
||||
|
||||
static void set_display_sleep(const bool sleep);
|
||||
|
||||
static void charge_deep_sleep(const bool sleep);
|
||||
|
||||
static inline void check_fifo_isr() {
|
||||
if (!shared_memory.application_queue.is_empty()) {
|
||||
events_flag_isr(EVT_MASK_APPLICATION);
|
||||
|
||||
+29
-25
@@ -131,35 +131,39 @@ void TVView::on_channel_spectrum(
|
||||
// I was hoping that by doing this, I can have a longer buffer like 39936, then the frame will looks better vertically
|
||||
// however this is useless until now.
|
||||
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
// video_buffer[i+count*256] = spectrum_rgb4_lut[spectrum.db[i]];
|
||||
video_buffer_int[i + count * 256] = 255 - spectrum.db[i];
|
||||
// Left the original comment, but rewrote the function.
|
||||
|
||||
// Shift the last 128 pixels of the previous packet to the front
|
||||
// This overlap handles an x_correction shift of up to 128 pixels seamlessly.
|
||||
for (size_t i = 0; i < 128; i++) {
|
||||
window_buffer[i] = window_buffer[i + 256];
|
||||
}
|
||||
count = count + 1;
|
||||
if (count == 52 - 1) {
|
||||
ui::Color line_buffer[128];
|
||||
Coord line;
|
||||
uint32_t bmp_px;
|
||||
|
||||
/*for (line = 0; line < 104; line++)
|
||||
{
|
||||
for (bmp_px = 0; bmp_px < 128; bmp_px++)
|
||||
{
|
||||
//line_buffer[bmp_px] = video_buffer[bmp_px+line*128];
|
||||
line_buffer[bmp_px] = spectrum_rgb4_lut[video_buffer_int[bmp_px+line*128 + x_correction]];
|
||||
}
|
||||
// Load the new 256 pixels into the remainder of the buffer
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
window_buffer[i + 128] = 255 - spectrum.db[i];
|
||||
}
|
||||
|
||||
display.render_line({ 0, line + 100 }, 128, line_buffer);
|
||||
}*/
|
||||
for (line = 0; line < 208; line = line + 2) {
|
||||
for (bmp_px = 0; bmp_px < 128; bmp_px++) {
|
||||
// line_buffer[bmp_px] = video_buffer[bmp_px+line*128];
|
||||
line_buffer[bmp_px] = spectrum_rgb4_lut[video_buffer_int[bmp_px + line / 2 * 128 + x_correction]];
|
||||
}
|
||||
ui::Color line_buffer[128];
|
||||
|
||||
display.render_line({0, line + 100}, 128, line_buffer);
|
||||
display.render_line({0, line + 101}, 128, line_buffer);
|
||||
}
|
||||
// Render the first line of this batch (matches original doubled-height behavior)
|
||||
for (size_t bmp_px = 0; bmp_px < 128; bmp_px++) {
|
||||
line_buffer[bmp_px] = spectrum_rgb4_lut[window_buffer[bmp_px + x_correction]];
|
||||
}
|
||||
display.render_line({0, (Coord)(count * 4 + 100)}, 128, line_buffer);
|
||||
display.render_line({0, (Coord)(count * 4 + 101)}, 128, line_buffer);
|
||||
|
||||
// Render the second line of this batch
|
||||
for (size_t bmp_px = 0; bmp_px < 128; bmp_px++) {
|
||||
line_buffer[bmp_px] = spectrum_rgb4_lut[window_buffer[bmp_px + 128 + x_correction]];
|
||||
}
|
||||
display.render_line({0, (Coord)(count * 4 + 102)}, 128, line_buffer);
|
||||
display.render_line({0, (Coord)(count * 4 + 103)}, 128, line_buffer);
|
||||
|
||||
count++;
|
||||
|
||||
// Reset at 52 (52 * 2 lines = 104 lines total)
|
||||
if (count >= 52) {
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -79,8 +79,9 @@ class TVView : public Widget {
|
||||
void paint(Painter& painter) override;
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
void on_adjust_xcorr(uint8_t xcorr);
|
||||
// ui::Color video_buffer[13312];
|
||||
uint8_t video_buffer_int[13312 + 128]{0}; // 128 is for the over length caused by x_correction
|
||||
// 256 samples from current callback + 128 overlap from the previous callback
|
||||
uint8_t window_buffer[384]{0};
|
||||
|
||||
uint32_t count = 0;
|
||||
uint8_t x_correction = 0;
|
||||
|
||||
|
||||
@@ -77,6 +77,10 @@ void ExtSensorsView::on_any() {
|
||||
has_data = true;
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_show() {
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(3);
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_gps(const GPSPosDataMessage* msg) {
|
||||
on_any();
|
||||
std::string tmp = to_string_decimal(msg->lat, 5);
|
||||
|
||||
@@ -78,6 +78,8 @@ class ExtSensorsView : public View {
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
void on_environment(const EnvironmentDataMessage* msg);
|
||||
|
||||
void on_show() override;
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ SdOverUsbView::SdOverUsbView(NavigationView& nav)
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
|
||||
portapack::shutdown(true);
|
||||
portapack::shutdown(true, false);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
m0_halt();
|
||||
/* will not return*/
|
||||
|
||||
+11
-10
@@ -33,7 +33,7 @@ namespace ui::external_app::time_sink {
|
||||
|
||||
TimeSinkWaveformWidget::TimeSinkWaveformWidget(
|
||||
Rect parent_rect,
|
||||
const int16_t* data,
|
||||
const int8_t* data,
|
||||
size_t length,
|
||||
Color color)
|
||||
: Widget{parent_rect},
|
||||
@@ -71,10 +71,10 @@ void TimeSinkWaveformWidget::set_persistence_frames(uint8_t frames) {
|
||||
}
|
||||
}
|
||||
|
||||
Coord TimeSinkWaveformWidget::sample_to_y(const Rect& r, int16_t sample) const {
|
||||
Coord TimeSinkWaveformWidget::sample_to_y(const Rect& r, int8_t sample) const {
|
||||
const int32_t y_center = r.top() + (r.height() / 2);
|
||||
const int32_t y_span = std::max<int32_t>(1, r.height() - 1);
|
||||
const int32_t y = y_center - (static_cast<int32_t>(sample) * y_span) / 65536;
|
||||
const int32_t y = y_center - (static_cast<int32_t>(sample) * y_span) / 256;
|
||||
return static_cast<Coord>(std::clamp<int32_t>(y, r.top(), r.bottom() - 1));
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
|
||||
current_y_[x] = sample_to_y(r, data_[src_index]);
|
||||
}
|
||||
|
||||
// Draw first, then erase stale pixels so the trace never disappears mid-refresh.
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
display.draw_pixel(
|
||||
{static_cast<Coord>(r.left() + x), current_y_[x]},
|
||||
@@ -126,13 +125,13 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
|
||||
const size_t expired_slot = history_head_;
|
||||
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
const auto expired_y = history_y_[expired_slot][x];
|
||||
const auto expired_y = sample_to_y(r, history_samples_[expired_slot][x]);
|
||||
bool keep = (expired_y == current_y_[x]);
|
||||
|
||||
if (!keep) {
|
||||
for (size_t i = 1; i < history_count_; ++i) {
|
||||
const size_t slot = (history_head_ + i) % max_persistence_frames;
|
||||
if (history_y_[slot][x] == expired_y) {
|
||||
if (sample_to_y(r, history_samples_[slot][x]) == expired_y) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
@@ -151,7 +150,10 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
const size_t tail_slot = (history_head_ + history_count_) % max_persistence_frames;
|
||||
std::copy_n(current_y_.begin(), columns, history_y_[tail_slot].begin());
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
const size_t src_index = (x * length_) / columns;
|
||||
history_samples_[tail_slot][x] = data_[src_index];
|
||||
}
|
||||
++history_count_;
|
||||
}
|
||||
|
||||
@@ -338,8 +340,7 @@ void TimeSinkView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
const size_t src_index =
|
||||
(trigger_index + offset) % source_count;
|
||||
const int32_t centered = static_cast<int32_t>(spectrum.db[src_index]) - 128;
|
||||
const int32_t scaled = centered * 256;
|
||||
waveform_buffer[x] = static_cast<int16_t>(std::clamp<int32_t>(scaled, -32768, 32767));
|
||||
waveform_buffer[x] = static_cast<int8_t>(std::clamp<int32_t>(centered, -128, 127));
|
||||
}
|
||||
|
||||
waveform.set_dirty();
|
||||
@@ -349,4 +350,4 @@ void TimeSinkView::on_freqchg(int64_t freq) {
|
||||
field_frequency.set_value(freq);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::time_sink
|
||||
} // namespace ui::external_app::time_sink
|
||||
@@ -40,7 +40,7 @@ constexpr size_t time_sink_waveform_points = 240;
|
||||
|
||||
class TimeSinkWaveformWidget : public Widget {
|
||||
public:
|
||||
TimeSinkWaveformWidget(Rect parent_rect, const int16_t* data, size_t length, Color color);
|
||||
TimeSinkWaveformWidget(Rect parent_rect, const int8_t* data, size_t length, Color color);
|
||||
TimeSinkWaveformWidget(const TimeSinkWaveformWidget&) = delete;
|
||||
TimeSinkWaveformWidget(TimeSinkWaveformWidget&&) = delete;
|
||||
TimeSinkWaveformWidget& operator=(const TimeSinkWaveformWidget&) = delete;
|
||||
@@ -56,13 +56,13 @@ class TimeSinkWaveformWidget : public Widget {
|
||||
static constexpr size_t max_persistence_frames = 16; // this is sad that we cant have 32 histories in ext app due to memory constraints
|
||||
|
||||
void reset_cache();
|
||||
Coord sample_to_y(const Rect& r, int16_t sample) const;
|
||||
Coord sample_to_y(const Rect& r, int8_t sample) const;
|
||||
|
||||
const int16_t* data_;
|
||||
const int8_t* data_;
|
||||
size_t length_;
|
||||
Color color_;
|
||||
std::array<Coord, max_columns> current_y_{};
|
||||
std::array<std::array<Coord, max_columns>, max_persistence_frames> history_y_{};
|
||||
std::array<std::array<int8_t, max_columns>, max_persistence_frames> history_samples_{};
|
||||
size_t history_count_{0};
|
||||
size_t history_head_{0};
|
||||
uint8_t persistence_frames_{1};
|
||||
@@ -120,7 +120,7 @@ class TimeSinkView : public View {
|
||||
{"trigger_level"sv, &trigger_level},
|
||||
}};
|
||||
|
||||
int16_t waveform_buffer[waveform_points]{0};
|
||||
int8_t waveform_buffer[waveform_points]{0};
|
||||
ChannelSpectrumFIFO* fifo = nullptr;
|
||||
|
||||
Labels labels{
|
||||
@@ -235,4 +235,4 @@ class TimeSinkView : public View {
|
||||
|
||||
} // namespace ui::external_app::time_sink
|
||||
|
||||
#endif // __UI_TIME_SINK_APP_H__
|
||||
#endif // __UI_TIME_SINK_APP_H__
|
||||
@@ -193,15 +193,12 @@ void WaterfallDesignerView::on_profile_changed(std::filesystem::path new_profile
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") continue;
|
||||
profile_levels.push_back(line);
|
||||
}
|
||||
|
||||
for (auto& line : profile_levels) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") {
|
||||
profile_levels.erase(std::remove(profile_levels.begin(), profile_levels.end(), line), profile_levels.end());
|
||||
}
|
||||
|
||||
// remove line end \n etc
|
||||
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path cu8_ext{u".CU8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
|
||||
Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
|
||||
@@ -543,12 +544,14 @@ bool path_iequal(
|
||||
|
||||
bool is_cxx_capture_file(const path& filename) {
|
||||
auto ext = filename.extension();
|
||||
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext);
|
||||
return path_iequal(c8_ext, ext) || path_iequal(cu8_ext, ext) || path_iequal(c16_ext, ext);
|
||||
}
|
||||
|
||||
uint8_t capture_file_sample_size(const path& filename) {
|
||||
if (path_iequal(filename.extension(), c8_ext))
|
||||
return sizeof(complex8_t);
|
||||
if (path_iequal(filename.extension(), cu8_ext))
|
||||
return sizeof(complexu8_t);
|
||||
if (path_iequal(filename.extension(), c16_ext))
|
||||
return sizeof(complex16_t);
|
||||
return 0;
|
||||
|
||||
@@ -28,13 +28,11 @@ namespace fs = std::filesystem;
|
||||
const std::filesystem::path default_gradient_file = u"waterfall.txt";
|
||||
|
||||
Gradient::Gradient() {
|
||||
prev_index = 0;
|
||||
prev_r = 0;
|
||||
prev_g = 0;
|
||||
prev_b = 0;
|
||||
step(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void Gradient::set_default() {
|
||||
step(0, 0, 0, 0);
|
||||
step(86, 0, 0, 255);
|
||||
step(171, 0, 255, 0);
|
||||
step(255, 255, 0, 0);
|
||||
@@ -47,6 +45,9 @@ bool Gradient::load_file(const std::filesystem::path& file_path) {
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
// make sure the table starts at 0
|
||||
step(0, 0, 0, 0);
|
||||
|
||||
auto reader = FileLineReader(gradient_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
@@ -73,17 +74,24 @@ bool Gradient::load_file(const std::filesystem::path& file_path) {
|
||||
}
|
||||
}
|
||||
|
||||
// extend the gradient from the current index to the end of the table
|
||||
step(255, prev_r, prev_g, prev_b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gradient::step(int16_t index, int16_t r, int16_t g, int16_t b) {
|
||||
for (int16_t i = prev_index; i <= index; i++) {
|
||||
float x = (float)(i - prev_index) / (index - prev_index);
|
||||
float y = 1.0f - x;
|
||||
// prevent division by 0 if index == prev_index
|
||||
int16_t range = index - prev_index;
|
||||
if (range == 0) range = 1;
|
||||
|
||||
for (int16_t i = prev_index; i <= index; i++) {
|
||||
int16_t t = i - prev_index;
|
||||
|
||||
int16_t new_r = prev_r + (t * (r - prev_r) + range / 2) / range;
|
||||
int16_t new_g = prev_g + (t * (g - prev_g) + range / 2) / range;
|
||||
int16_t new_b = prev_b + (t * (b - prev_b) + range / 2) / range;
|
||||
|
||||
int16_t new_r = prev_r * y + r * x;
|
||||
int16_t new_g = prev_g * y + g * x;
|
||||
int16_t new_b = prev_b * y + b * x;
|
||||
if (i <= 255) lut[i] = ui::Color(new_r, new_g, new_b);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,5 +103,13 @@ data_t SPI::transfer_word(const Direction direction, const address_t address, co
|
||||
return data_in;
|
||||
}
|
||||
|
||||
void SPI::power_down() {
|
||||
// A 0x01 address the MIX_CTRL register. A 0x0000
|
||||
transfer_word(Direction::Write, 0x01, 0x0000);
|
||||
|
||||
// a chip 300 µA-es Power Down
|
||||
transfer_word(Direction::Write, 0x00, 0x0000);
|
||||
}
|
||||
|
||||
} // namespace spi
|
||||
} // namespace rffc507x
|
||||
|
||||
@@ -41,6 +41,7 @@ class SPI {
|
||||
};
|
||||
|
||||
void init();
|
||||
void power_down();
|
||||
|
||||
reg_t read(const address_t address) {
|
||||
return transfer_word(Direction::Read, address, 0);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext = u".C8";
|
||||
static const fs::path cu8_ext = u".CU8";
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
@@ -59,21 +60,42 @@ void c8_to_c16(const void* buffer, File::Size bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert cu8 buffer to c16 buffer.
|
||||
// Same buffer used for input & output; input size is bytes; output size is 2*bytes.
|
||||
void cu8_to_c16(const void* buffer, File::Size bytes) {
|
||||
complexu8_t* src = (complexu8_t*)buffer;
|
||||
complex16_t* dest = (complex16_t*)buffer;
|
||||
uint32_t i = bytes / sizeof(complexu8_t);
|
||||
|
||||
if (i != 0) {
|
||||
do {
|
||||
i--;
|
||||
int16_t re_out = ((int16_t)src[i].real() - 128) * 256;
|
||||
int16_t im_out = ((int16_t)src[i].imag() - 128) * 256;
|
||||
dest[i] = {(int16_t)re_out, (int16_t)im_out};
|
||||
} while (i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
// Automatically enables C8/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertReader::open(const std::filesystem::path& filename) {
|
||||
convert_c8_to_c16 = path_iequal(filename.extension(), c8_ext);
|
||||
convert_cu8_to_c16 = path_iequal(filename.extension(), cu8_ext);
|
||||
return file_.open(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion enabled, half the number of bytes are read from the file & expanded to fill the whole buffer.
|
||||
File::Result<File::Size> FileConvertReader::read(void* const buffer, const File::Size bytes) {
|
||||
auto read_result = file_.read(buffer, convert_c8_to_c16 ? bytes / 2 : bytes);
|
||||
auto read_result = file_.read(buffer, (convert_c8_to_c16 || convert_cu8_to_c16) ? bytes / 2 : bytes);
|
||||
if (read_result.is_ok()) {
|
||||
if (convert_c8_to_c16) {
|
||||
file_convert::c8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
} else if (convert_cu8_to_c16) {
|
||||
file_convert::cu8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
}
|
||||
bytes_read_ += read_result.value();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class FileConvertReader : public stream::Reader {
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c8_to_c16{};
|
||||
bool convert_cu8_to_c16{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "event_m0.hpp"
|
||||
#include "nvic.h"
|
||||
#include "lpc43xx.inc"
|
||||
|
||||
static Thread* thread_rtc_event = NULL;
|
||||
|
||||
@@ -36,6 +38,84 @@ void rtc_interrupt_enable() {
|
||||
nvicEnableVector(RTC_IRQn, CORTEX_PRIORITY_MASK(LPC_RTC_IRQ_PRIORITY));
|
||||
}
|
||||
|
||||
void rtc_reset_default() {
|
||||
// 1. FULL RTC CLEANUP/RESET
|
||||
LPC_RTC->CIIR = 0;
|
||||
LPC_RTC->AMR = 0xFF; // Disable all alarms
|
||||
LPC_RTC->ILR = 3; // Clear stuck interrupt flags
|
||||
LPC_RTC->ASEC = 0;
|
||||
LPC_RTC->AMIN = 0;
|
||||
LPC_RTC->AHRS = 0;
|
||||
|
||||
// 2. RESET EVENT ROUTER
|
||||
// Reset to edge-triggered (Garantálja, hogy nem ragad be az ébresztés!)
|
||||
LPC_EVENTROUTER->EDGE |= (1 << 5);
|
||||
|
||||
// DISABLE RTC channel routing
|
||||
LPC_EVENTROUTER->CLR_EN = (1 << 5);
|
||||
|
||||
// Clear pending events
|
||||
LPC_EVENTROUTER->CLR_STAT = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
void rtc_wakeup_init() {
|
||||
rtc_reset_default();
|
||||
|
||||
// 1. Force RTC Clock (In case it was modified by the app)
|
||||
// Bit 0: Enable, Bit 4: Calibration OFF
|
||||
LPC_RTC->CCR = (1 << 0);
|
||||
|
||||
// 2. EVENT ROUTER CONFIGURATION
|
||||
LPC_EVENTROUTER->HILO |= (1 << 5);
|
||||
LPC_EVENTROUTER->EDGE |= (1 << 5); // <-- JAVÍTVA! (Itt volt a hiba a te kódodban)
|
||||
LPC_EVENTROUTER->CLR_STAT = 0xFFFFFFFF; // Clear pending events
|
||||
LPC_EVENTROUTER->SET_EN = (1 << 5); // Enable RTC channel routing
|
||||
|
||||
// 3. NVIC Cleanup
|
||||
NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
NVIC_ClearPendingIRQ(EVENTROUTER_IRQn);
|
||||
NVIC_EnableIRQ(RTC_IRQn);
|
||||
NVIC_EnableIRQ(EVENTROUTER_IRQn);
|
||||
}
|
||||
|
||||
void rtc_wakeup(uint32_t sleep_seconds) {
|
||||
// 1. Safety cleanup after application tasks
|
||||
LPC_RTC->CCR &= ~(1 << 4); // Disable calibration (Important!)
|
||||
|
||||
uint32_t sec = LPC_RTC->SEC;
|
||||
uint32_t min = LPC_RTC->MIN;
|
||||
uint32_t hrs = LPC_RTC->HRS;
|
||||
|
||||
sec += sleep_seconds;
|
||||
while (sec >= 60) {
|
||||
sec -= 60;
|
||||
min++;
|
||||
}
|
||||
while (min >= 60) {
|
||||
min -= 60;
|
||||
hrs++;
|
||||
}
|
||||
while (hrs >= 24) {
|
||||
hrs -= 24;
|
||||
}
|
||||
|
||||
// 2. Write values to alarm registers
|
||||
LPC_RTC->ASEC = sec;
|
||||
LPC_RTC->AMIN = min;
|
||||
LPC_RTC->AHRS = hrs;
|
||||
|
||||
// Mask for SEC, MIN, HRS (Disable all other alarm triggers)
|
||||
uint32_t mask = 0xFF ^ ((1 << 0) | (1 << 1) | (1 << 2));
|
||||
LPC_RTC->AMR = mask;
|
||||
|
||||
// Verify write (Sync with RTC domain)
|
||||
while (LPC_RTC->ASEC != sec);
|
||||
while (LPC_RTC->AMR != mask);
|
||||
|
||||
// Brief extra delay to allow internal logic to settle/latch
|
||||
for (volatile int i = 0; i < 5000; i++) __asm__("nop");
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(RTC_IRQHandler) {
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
#ifndef __IPC_RTC_H__
|
||||
#define __IPC_RTC_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void rtc_interrupt_enable();
|
||||
void rtc_reset_default();
|
||||
void rtc_wakeup_init();
|
||||
void rtc_wakeup(uint32_t sleep_seconds);
|
||||
|
||||
#endif /*__IPC_RTC_H__*/
|
||||
|
||||
@@ -141,6 +141,8 @@ Continuous (Fox-oring)
|
||||
#include <string.h>
|
||||
#include "i2cdevmanager.hpp"
|
||||
|
||||
#include "lpc43xx.inc"
|
||||
|
||||
#include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */
|
||||
rffc507x::RFFC507x first_if;
|
||||
ui::SystemView* system_view_ptr;
|
||||
@@ -181,6 +183,8 @@ static void event_loop() {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
rtc_reset_default();
|
||||
|
||||
#ifndef PRALINE // Do not perform quick set up of GP01_RFF507X = 1 for PRALINE
|
||||
first_if.init(); /* To avoid initial short Ant_DC_Bias pulse ,we need quick set up GP01_RFF507X =1 */
|
||||
#endif
|
||||
|
||||
@@ -339,6 +339,20 @@ static void shutdown_base() {
|
||||
clock_manager.shutdown();
|
||||
}
|
||||
|
||||
static void shutdown_base_12mhz() {
|
||||
i2c0.stop();
|
||||
|
||||
set_clock_config(clock_config_irc);
|
||||
|
||||
cgu::pll1::disable();
|
||||
|
||||
set_idivc_base_clocks(cgu::CLK_SEL::IRC);
|
||||
|
||||
i2c0.start(i2c_config_boot_clock);
|
||||
|
||||
clock_manager.shutdown();
|
||||
}
|
||||
|
||||
static void set_cpu_clock_speed() {
|
||||
/* Incantation from LPC43xx UM10503 section 12.2.1.1, to bring the M4
|
||||
* core clock speed to the 110 - 204MHz range.
|
||||
@@ -698,7 +712,7 @@ init_status_t init() {
|
||||
return return_code;
|
||||
}
|
||||
|
||||
void shutdown(const bool leave_screen_on) {
|
||||
void shutdown(const bool leave_screen_on, const bool slow_clock) {
|
||||
gpdma::controller.disable();
|
||||
|
||||
if (!leave_screen_on) {
|
||||
@@ -712,7 +726,11 @@ void shutdown(const bool leave_screen_on) {
|
||||
|
||||
hackrf::cpld::init_from_eeprom();
|
||||
|
||||
shutdown_base();
|
||||
if (slow_clock) {
|
||||
shutdown_base_12mhz();
|
||||
} else {
|
||||
shutdown_base();
|
||||
}
|
||||
}
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt) {
|
||||
|
||||
@@ -78,7 +78,7 @@ void set_antenna_bias(const bool v);
|
||||
bool get_antenna_bias();
|
||||
|
||||
init_status_t init();
|
||||
void shutdown(const bool leave_screen_on = false);
|
||||
void shutdown(const bool leave_screen_on = false, const bool slow_clock = false);
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt);
|
||||
|
||||
|
||||
@@ -606,4 +606,4 @@ uint32_t register_read(const size_t register_number) {
|
||||
|
||||
} /* namespace debug */
|
||||
|
||||
} /* namespace radio */
|
||||
} /* namespace radio */
|
||||
@@ -136,4 +136,4 @@ uint32_t register_read(const size_t register_number);
|
||||
|
||||
} /* namespace radio */
|
||||
|
||||
#endif /*__RADIO_H__*/
|
||||
#endif /*__RADIO_H__*/
|
||||
@@ -104,7 +104,7 @@ std::string to_string_bin(
|
||||
if (l >= 33) l = 32;
|
||||
char p[33];
|
||||
for (uint8_t c = 0; c < l; c++) {
|
||||
if (n & (1 << (l - 1 - c)))
|
||||
if (n & (1UL << (l - 1 - c)))
|
||||
p[c] = '1';
|
||||
else
|
||||
p[c] = '0';
|
||||
@@ -130,28 +130,19 @@ std::string to_string_dec_uint(
|
||||
return q;
|
||||
}
|
||||
|
||||
std::string to_string_dec_int(
|
||||
const int32_t n,
|
||||
const int32_t l,
|
||||
const char fill) {
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill) {
|
||||
const size_t negative = (n < 0) ? 1 : 0;
|
||||
uint32_t n_abs = negative ? -n : n;
|
||||
|
||||
char p[16];
|
||||
uint32_t n_abs = negative ? static_cast<uint32_t>(-(int64_t)n) : static_cast<uint32_t>(n);
|
||||
char p[24];
|
||||
int32_t safe_l = std::min<int32_t>(l, sizeof(p) - 1);
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);
|
||||
|
||||
// Add sign.
|
||||
auto q = to_string_dec_uint_pad_internal(term, n_abs, safe_l - negative, fill);
|
||||
if (negative) {
|
||||
*(--q) = '-';
|
||||
}
|
||||
|
||||
// Right justify.
|
||||
// (This code is redundant and won't do anything if a fill character was specified)
|
||||
while ((term - q) < l) {
|
||||
*(--q) = ' ';
|
||||
while ((term - q) < safe_l) {
|
||||
*(--q) = (fill ? fill : ' ');
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
@@ -259,38 +250,32 @@ std::string to_string_time_ms(const uint32_t ms) {
|
||||
return final_str;
|
||||
}
|
||||
|
||||
static char* to_string_hex_internal(char* ptr, uint64_t value, uint8_t length) {
|
||||
if (length == 0)
|
||||
return ptr;
|
||||
|
||||
*(--ptr) = uint_to_char(value & 0xF, 16);
|
||||
return to_string_hex_internal(ptr, value >> 4, length - 1);
|
||||
}
|
||||
|
||||
std::string to_string_hex(uint64_t value, int32_t length) {
|
||||
constexpr uint8_t buffer_length = 33;
|
||||
char buffer[buffer_length];
|
||||
|
||||
char* ptr = &buffer[buffer_length - 1];
|
||||
*ptr = '\0';
|
||||
|
||||
length = std::min<uint8_t>(buffer_length - 1, length);
|
||||
return to_string_hex_internal(ptr, value, length);
|
||||
length = std::min<int32_t>(buffer_length - 1, length);
|
||||
for (int32_t i = 0; i < length; ++i) {
|
||||
*(--ptr) = uint_to_char(value & 0xF, 16);
|
||||
value >>= 4;
|
||||
}
|
||||
return std::string(ptr);
|
||||
}
|
||||
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length) {
|
||||
std::string str_return;
|
||||
str_return.reserve(length * 2);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++)
|
||||
str_return += to_string_hex(array[i], 2);
|
||||
|
||||
return str_return;
|
||||
std::string s;
|
||||
s.resize(length * 2);
|
||||
for (int i = 0; i < length; i++) {
|
||||
s[i * 2] = uint_to_char((array[i] >> 4) & 0xF, 16);
|
||||
s[i * 2 + 1] = uint_to_char(array[i] & 0xF, 16);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format) {
|
||||
std::string string{""};
|
||||
|
||||
string.reserve(20);
|
||||
if (format == YMDHMS) {
|
||||
string += to_string_dec_uint(value.year(), 4) + "-" +
|
||||
to_string_dec_uint(value.month(), 2, '0') + "-" +
|
||||
@@ -307,20 +292,33 @@ std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format) {
|
||||
}
|
||||
|
||||
std::string to_string_timestamp(const rtc::RTC& value) {
|
||||
return to_string_dec_uint(value.year(), 4, '0') +
|
||||
to_string_dec_uint(value.month(), 2, '0') +
|
||||
to_string_dec_uint(value.day(), 2, '0') +
|
||||
to_string_dec_uint(value.hour(), 2, '0') +
|
||||
to_string_dec_uint(value.minute(), 2, '0') +
|
||||
to_string_dec_uint(value.second(), 2, '0');
|
||||
std::string result;
|
||||
// YYYYMMDDHHMMSS = 14 characters
|
||||
result.reserve(14);
|
||||
result += to_string_dec_uint(value.year(), 4, '0');
|
||||
result += to_string_dec_uint(value.month(), 2, '0');
|
||||
result += to_string_dec_uint(value.day(), 2, '0');
|
||||
result += to_string_dec_uint(value.hour(), 2, '0');
|
||||
result += to_string_dec_uint(value.minute(), 2, '0');
|
||||
result += to_string_dec_uint(value.second(), 2, '0');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string_FAT_timestamp(const FATTimestamp& timestamp) {
|
||||
return to_string_dec_uint((timestamp.FAT_date >> 9) + 1980) + "-" +
|
||||
to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0') + "-" +
|
||||
to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0') + " " +
|
||||
to_string_dec_uint((timestamp.FAT_time >> 11), 2, '0') + ":" +
|
||||
to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
|
||||
std::string result;
|
||||
// YYYY-MM-DD HH:MM = 16 characters
|
||||
result.reserve(16);
|
||||
result += to_string_dec_uint((timestamp.FAT_date >> 9) + 1980);
|
||||
result += '-';
|
||||
result += to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0');
|
||||
result += '-';
|
||||
result += to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0');
|
||||
result += ' ';
|
||||
result += to_string_dec_uint((timestamp.FAT_time >> 11), 2, '0');
|
||||
result += ':';
|
||||
result += to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string_file_size(uint32_t file_size) {
|
||||
@@ -339,20 +337,25 @@ std::string to_string_file_size(uint32_t file_size) {
|
||||
}
|
||||
|
||||
std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length, bool noColon) {
|
||||
std::string string;
|
||||
|
||||
string += to_string_hex(macAddress[0], 2);
|
||||
|
||||
for (int i = 1; i < length; i++) {
|
||||
string += noColon ? to_string_hex(macAddress[i], 2) : ":" + to_string_hex(macAddress[i], 2);
|
||||
if (length == 0 || macAddress == nullptr) return "";
|
||||
std::string result;
|
||||
// Size = 2 chars per byte + 1 colon between bytes (if used)
|
||||
result.reserve((length * 2) + (noColon ? 0 : length - 1));
|
||||
constexpr char hex_chars[] = "0123456789ABCDEF";
|
||||
for (int i = 0; i < length; i++) {
|
||||
// Append the colon separator (if not the first byte)
|
||||
if (i > 0 && !noColon) {
|
||||
result += ':';
|
||||
}
|
||||
result += hex_chars[(macAddress[i] >> 4) & 0x0F];
|
||||
result += hex_chars[macAddress[i] & 0x0F];
|
||||
}
|
||||
|
||||
return string;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string_formatted_mac_address(const char* macAddress) {
|
||||
std::string formattedAddress;
|
||||
|
||||
formattedAddress.reserve(17);
|
||||
for (int i = 0; i < 12; i += 2) {
|
||||
if (i > 0) {
|
||||
formattedAddress += ':';
|
||||
@@ -377,16 +380,16 @@ void generateRandomMacAddress(char* macAddress) {
|
||||
|
||||
uint64_t readUntil(File& file, char* result, std::size_t maxBufferSize, char delimiter) {
|
||||
std::size_t bytesRead = 0;
|
||||
|
||||
if (maxBufferSize == 0) return 0;
|
||||
while (true) {
|
||||
char ch;
|
||||
File::Result<File::Size> readResult = file.read(&ch, 1);
|
||||
|
||||
if (readResult.is_ok() && readResult.value() > 0) {
|
||||
if (ch == delimiter) {
|
||||
// Found a space character, stop reading
|
||||
// Found the delimiter character, stop reading
|
||||
break;
|
||||
} else if (bytesRead < maxBufferSize) {
|
||||
} else if (bytesRead < maxBufferSize - 1) {
|
||||
// Append the character to the result if there's space
|
||||
result[bytesRead++] = ch;
|
||||
} else {
|
||||
@@ -408,12 +411,13 @@ std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precisi
|
||||
const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
|
||||
std::string string{""};
|
||||
uint32_t prefix_index = base_unit;
|
||||
if (prefix_index > 6) prefix_index = 6;
|
||||
double integer_part;
|
||||
double fractional_part;
|
||||
|
||||
precision = std::min((uint32_t)4, precision);
|
||||
|
||||
while (n > 1000) {
|
||||
while (n > 1000 && prefix_index < 6) {
|
||||
n /= 1000.0;
|
||||
prefix_index++;
|
||||
}
|
||||
@@ -426,7 +430,7 @@ std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precisi
|
||||
if (precision)
|
||||
string += '.' + to_string_dec_uint(fractional_part, precision, '0');
|
||||
|
||||
if (prefix_index != 3)
|
||||
if (unit_prefix[prefix_index] != 0)
|
||||
string += unit_prefix[prefix_index];
|
||||
|
||||
return string;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "rtc_time.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include <algorithm>
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -60,6 +61,7 @@ BtnGridView::BtnGridView(
|
||||
}
|
||||
|
||||
BtnGridView::~BtnGridView() {
|
||||
ExternalItemsMenuLoader::unload_external_items();
|
||||
}
|
||||
|
||||
void BtnGridView::set_max_rows(int rows) {
|
||||
@@ -87,6 +89,7 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
|
||||
remove_child(item.get());
|
||||
|
||||
menu_item_views.clear();
|
||||
menu_item_views.shrink_to_fit();
|
||||
}
|
||||
|
||||
button_w = screen_width / rows_;
|
||||
@@ -137,6 +140,7 @@ void BtnGridView::set_arrow_down_enabled(bool enabled) {
|
||||
void BtnGridView::clear() {
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
menu_items.clear();
|
||||
menu_items.shrink_to_fit();
|
||||
|
||||
// TODO(u-foka): Clean up my mess, move this somewhere to clear memory when the view is not visible, but not to be confused with clearing the menu items...
|
||||
for (auto& item : menu_item_views)
|
||||
@@ -144,10 +148,11 @@ void BtnGridView::clear() {
|
||||
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
menu_item_views.clear();
|
||||
menu_item_views.shrink_to_fit();
|
||||
}
|
||||
|
||||
void BtnGridView::add_items(std::initializer_list<GridItem> new_items, bool inhibit_update) {
|
||||
for (auto item : new_items) {
|
||||
for (const auto& item : new_items) {
|
||||
if (!blacklisted_app(item))
|
||||
menu_items.push_back(item);
|
||||
}
|
||||
@@ -389,39 +394,34 @@ bool BtnGridView::on_encoder(const EncoderEvent event) {
|
||||
|
||||
/* BlackList ******************************************************/
|
||||
|
||||
std::unique_ptr<char> blacklist_ptr{};
|
||||
size_t blacklist_len{};
|
||||
std::string blacklist_data{};
|
||||
|
||||
void load_blacklist() {
|
||||
File f;
|
||||
|
||||
auto error = f.open(BLACKLIST);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
// allocating two extra bytes for leading & trailing commas
|
||||
blacklist_ptr = std::unique_ptr<char>(new char[f.size() + 2]);
|
||||
if (f.read(blacklist_ptr.get() + 1, f.size())) {
|
||||
blacklist_len = f.size() + 2;
|
||||
|
||||
// replace any CR/LF characters with comma delineator, and add comma prefix/suffix, to simplify searching
|
||||
char* ptr = blacklist_ptr.get();
|
||||
*ptr = ',';
|
||||
*(ptr + blacklist_len - 1) = ',';
|
||||
for (size_t i = 0; i < blacklist_len; i++, ptr++) {
|
||||
if (*ptr == 0x0D || *ptr == 0x0A)
|
||||
*ptr = ',';
|
||||
// Resize string to fit file + 2 commas, filling it with commas by default
|
||||
blacklist_data.assign(f.size() + 2, ',');
|
||||
// Read directly into the string's buffer (offset by 1 to leave the first comma)
|
||||
if (f.read(blacklist_data.data() + 1, f.size())) {
|
||||
// Replace any CR/LF characters with commas
|
||||
for (char& c : blacklist_data) {
|
||||
if (c == '\r' || c == '\n') {
|
||||
c = ',';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blacklist_data.clear(); // Clear if read fails
|
||||
}
|
||||
}
|
||||
|
||||
bool BtnGridView::blacklisted_app(GridItem new_item) {
|
||||
std::string app_name = "," + new_item.text + ",";
|
||||
|
||||
if (blacklist_len < app_name.size())
|
||||
if (blacklist_data.size() < app_name.size())
|
||||
return false;
|
||||
|
||||
return std::search(blacklist_ptr.get(), blacklist_ptr.get() + blacklist_len, app_name.begin(), app_name.end()) < blacklist_ptr.get() + blacklist_len;
|
||||
return blacklist_data.find(app_name) != std::string::npos;
|
||||
}
|
||||
|
||||
void BtnGridView::page_up() {
|
||||
|
||||
@@ -251,15 +251,13 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui::Color* zoom_out_buffer = new ui::Color[(pixels * (-map_zoom))];
|
||||
ui::Color zoom_out_buffer[(pixels * (-map_zoom))];
|
||||
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
|
||||
|
||||
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
|
||||
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
|
||||
for (int i = 0; i < width; i++) {
|
||||
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
|
||||
}
|
||||
delete[] zoom_out_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,8 +331,9 @@ void GeoMap::set_osm_max_zoom(bool changeboth) {
|
||||
for (uint8_t i = map_osm_zoom; i > 0; i--) {
|
||||
int tile_x = lon2tile(lon_, i);
|
||||
int tile_y = lat2tile(lat_, i);
|
||||
std::string filename = "/OSM/" + to_string_dec_int(i) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp";
|
||||
std::filesystem::path file_path(filename);
|
||||
char path_buffer[64];
|
||||
snprintf(path_buffer, sizeof(path_buffer), "/OSM/%d/%d/%d.bmp", i, tile_x, tile_y);
|
||||
std::filesystem::path file_path(path_buffer);
|
||||
if (file_exists(file_path)) {
|
||||
map_osm_real_zoom = i;
|
||||
if (changeboth) map_osm_zoom = i;
|
||||
@@ -347,7 +346,7 @@ void GeoMap::set_osm_max_zoom(bool changeboth) {
|
||||
|
||||
// checks if the tile file presents or not. to determine if we got osm or not
|
||||
uint8_t GeoMap::find_osm_file_tile() {
|
||||
std::string filename = "/OSM/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + ".bmp";
|
||||
std::string filename = "/OSM/0/0/0.bmp";
|
||||
std::filesystem::path file_path(filename);
|
||||
if (file_exists(file_path)) return 1;
|
||||
return 0; // not found
|
||||
@@ -456,22 +455,24 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
|
||||
display.fill_rectangle(error_rect, Theme::getInstance()->bg_darkest->background);
|
||||
return false;
|
||||
}
|
||||
std::vector<ui::Color> line(clip_w);
|
||||
|
||||
map_line_buffer.resize(clip_w);
|
||||
|
||||
if (bmp.is_bottomup()) {
|
||||
for (int y = clip_h - 1; y >= 0; --y) {
|
||||
int source_row = src_y + y;
|
||||
int dest_row = dest_y + y;
|
||||
bmp.seek(src_x, source_row);
|
||||
bmp.read_next_px_cnt(line.data(), clip_w, false);
|
||||
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
|
||||
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
|
||||
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
|
||||
}
|
||||
} else {
|
||||
for (int y = 0; y < clip_h; ++y) {
|
||||
int source_row = src_y + y;
|
||||
int dest_row = dest_y + y;
|
||||
bmp.seek(src_x, source_row);
|
||||
bmp.read_next_px_cnt(line.data(), clip_w, false);
|
||||
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
|
||||
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
|
||||
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -479,11 +480,11 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
|
||||
|
||||
void GeoMap::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
std::vector<ui::Color> map_line_buffer;
|
||||
map_line_buffer.resize(r.width());
|
||||
int16_t zoom_seek_x, zoom_seek_y;
|
||||
|
||||
if (!use_osm) {
|
||||
map_line_buffer.resize(r.width());
|
||||
|
||||
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
|
||||
if (map_zoom <= 1) {
|
||||
// Zooming out, or no zoom
|
||||
@@ -755,7 +756,7 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
|
||||
display.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
|
||||
}
|
||||
|
||||
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
|
||||
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string& itemTag, const Color color, const Color fontColor, const Color backColor) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
int tagOffset = 10;
|
||||
|
||||
@@ -247,7 +247,7 @@ class GeoMap : public Widget {
|
||||
ui::Point item_rect_pixel(GeoMarker& item);
|
||||
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
|
||||
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string& itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
void draw_mypos(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
@@ -264,6 +264,9 @@ class GeoMap : public Widget {
|
||||
double lat_to_pixel_y_tile(double lat, int zoom);
|
||||
double tile_pixel_x_to_lon(int x, int zoom);
|
||||
double tile_pixel_y_to_lat(int y, int zoom);
|
||||
|
||||
std::vector<ui::Color> map_line_buffer{};
|
||||
|
||||
uint8_t map_osm_zoom{5};
|
||||
uint8_t map_osm_real_zoom{5};
|
||||
double viewport_top_left_px = 0;
|
||||
|
||||
@@ -117,6 +117,7 @@ void MenuView::set_parent_rect(const Rect new_parent_rect) {
|
||||
remove_child(item.get());
|
||||
|
||||
menu_item_views.clear();
|
||||
menu_item_views.shrink_to_fit();
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < displayed_max; c++) {
|
||||
@@ -149,6 +150,7 @@ void MenuView::clear() {
|
||||
item->set_item(nullptr);
|
||||
|
||||
menu_items.clear();
|
||||
menu_items.shrink_to_fit();
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
}
|
||||
@@ -157,16 +159,15 @@ size_t MenuView::item_count() const {
|
||||
return menu_items.size();
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
menu_items.push_back(new_item);
|
||||
void MenuView::add_item(MenuItem&& new_item) {
|
||||
menu_items.push_back(std::move(new_item));
|
||||
|
||||
update_items();
|
||||
}
|
||||
|
||||
void MenuView::add_items(std::initializer_list<MenuItem> new_items) {
|
||||
for (auto item : new_items) {
|
||||
add_item(item);
|
||||
}
|
||||
menu_items.insert(menu_items.end(), new_items);
|
||||
update_items();
|
||||
}
|
||||
|
||||
void MenuView::update_items() {
|
||||
|
||||
@@ -83,7 +83,10 @@ class MenuView : public View {
|
||||
|
||||
~MenuView();
|
||||
|
||||
void add_item(MenuItem new_item);
|
||||
// This function takes a temporary to avoid copies and heap allocations.
|
||||
// To pass a variable 'item', use add_item(std::move(item)),
|
||||
// or construct the MenuItem in the call, add_item(MenuItem{...})
|
||||
void add_item(MenuItem&& new_item);
|
||||
void add_items(std::initializer_list<MenuItem> new_items);
|
||||
void clear();
|
||||
size_t item_count() const;
|
||||
|
||||
@@ -9,7 +9,13 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
|
||||
/* static */ std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> ExternalItemsMenuLoader::bitmaps;
|
||||
|
||||
// to save ram when entering an app
|
||||
void ExternalItemsMenuLoader::unload_external_items() {
|
||||
bitmaps.clear();
|
||||
bitmaps.shrink_to_fit();
|
||||
}
|
||||
|
||||
// iterates over all possible ext apps-s, and if it is runnable on the current system, it'll call the callback, and pass minimal info. used to print to console, and for autostart setting's app list. where the minimal info is enough
|
||||
// please keep in sync with load_external_items
|
||||
@@ -97,8 +103,9 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
bitmaps.clear();
|
||||
bitmaps.shrink_to_fit();
|
||||
|
||||
std::vector<GridItemEx> external_apps;
|
||||
|
||||
@@ -126,8 +133,8 @@ namespace ui {
|
||||
|
||||
gridItem.color = Color((uint16_t)appInfo->icon_color);
|
||||
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(appInfo->bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, appInfo, i]() {
|
||||
@@ -204,8 +211,8 @@ namespace ui {
|
||||
if (versionMatches) {
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
@@ -255,8 +262,8 @@ namespace ui {
|
||||
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
@@ -350,6 +357,8 @@ namespace ui {
|
||||
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
|
||||
return false;
|
||||
|
||||
nav.pop();
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
application_information.externalAppEntry(nav);
|
||||
return true;
|
||||
}
|
||||
@@ -386,7 +395,8 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(app_image);
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
nav.replace<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -400,7 +410,8 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(app_image);
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
nav.replace<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,30 +27,44 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
#include "standalone_app.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include "file.hpp"
|
||||
|
||||
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <size_t Width, size_t Height>
|
||||
class DynamicBitmap {
|
||||
public:
|
||||
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8); // one bit per pixel
|
||||
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8);
|
||||
|
||||
// Main constructor
|
||||
DynamicBitmap(const uint8_t data[buffer_size])
|
||||
: _buffer(buffer_size, 0),
|
||||
_bitmap{new Bitmap{{Width, Height}, &_buffer[0]}} {
|
||||
memcpy(&_buffer[0], data, buffer_size);
|
||||
: _bitmap{{Width, Height}, _buffer.data()} {
|
||||
std::memcpy(_buffer.data(), data, buffer_size);
|
||||
}
|
||||
|
||||
const Bitmap* bitmap() { return _bitmap.get(); }
|
||||
DynamicBitmap(const DynamicBitmap& other)
|
||||
: _buffer(other._buffer),
|
||||
_bitmap{{Width, Height}, _buffer.data()} {}
|
||||
|
||||
DynamicBitmap(DynamicBitmap&& other) noexcept
|
||||
: _buffer(std::move(other._buffer)),
|
||||
_bitmap{{Width, Height}, _buffer.data()} {}
|
||||
|
||||
DynamicBitmap& operator=(const DynamicBitmap& other) = delete;
|
||||
DynamicBitmap& operator=(DynamicBitmap&& other) noexcept = delete;
|
||||
|
||||
// Destructor (Default is fine, no heap memory to free manually)
|
||||
~DynamicBitmap() = default;
|
||||
|
||||
const Bitmap* bitmap() const { return &_bitmap; }
|
||||
|
||||
private:
|
||||
// Allocating both members so the class is movable without invalidation.
|
||||
std::vector<uint8_t> _buffer;
|
||||
std::unique_ptr<Bitmap> _bitmap;
|
||||
// Order matters: _buffer must be declared before _bitmap
|
||||
// so it is initialized first and its .data() pointer is valid.
|
||||
std::array<uint8_t, buffer_size> _buffer{};
|
||||
Bitmap _bitmap{};
|
||||
};
|
||||
|
||||
class ExternalItemsMenuLoader {
|
||||
@@ -65,9 +79,10 @@ class ExternalItemsMenuLoader {
|
||||
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, bool module_included = false);
|
||||
static void unload_external_items();
|
||||
|
||||
private:
|
||||
static std::vector<DynamicBitmap<16, 16>> bitmaps;
|
||||
static std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> bitmaps;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -82,82 +82,61 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool CstrCmp::operator()(const char* a, const char* b) const {
|
||||
return strcmp(a, b) < 0;
|
||||
}
|
||||
|
||||
static NavigationView::AppMap generate_app_map(const NavigationView::AppList& appList) {
|
||||
NavigationView::AppMap out;
|
||||
|
||||
for (auto& app : appList) {
|
||||
if (app.id == nullptr) {
|
||||
// Skip items with no id
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = out.emplace(app.id, app);
|
||||
if (!res.second) {
|
||||
chDbgPanic("Application cannot be added, ID not unique!");
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
|
||||
const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, new ViewFactory<TransceiversMenuView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
/* RX ********************************************************************/
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
/* TX ********************************************************************/
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* TRX ********************************************************************/
|
||||
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
};
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReceiversMenuView>(nav); }},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransmittersMenuView>(nav); }},
|
||||
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransceiversMenuView>(nav); }},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReconView>(nav); }},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<CaptureAppView>(nav); }},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<PlaylistView>(nav); }},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GlassView>(nav); }},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<UtilitiesMenuView>(nav); }},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GamesMenuView>(nav); }},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SettingsMenuView>(nav); }},
|
||||
|
||||
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
|
||||
/* RX ********************************************************************/
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ADSBRxView>(nav); }},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AISAppView>(nav); }},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSRXView>(nav); }},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AnalogAudioView>(nav); }},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLERxView>(nav); }},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<POCSAGAppView>(nav); }},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SondeView>(nav); }},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SearchView>(nav); }},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SubGhzDView>(nav); }},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<WeatherView>(nav); }},
|
||||
|
||||
/* TX ********************************************************************/
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSTXView>(nav); }},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLETxView>(nav); }},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<EncodersView>(nav); }},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<RDSView>(nav); }},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TouchTunesView>(nav); }},
|
||||
|
||||
/* TRX ********************************************************************/
|
||||
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<MicTXView>(nav); }},
|
||||
|
||||
/* UTILITIES *************************************************************/
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FileManagerView>(nav); }},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FrequencyManagerView>(nav); }},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<IQTrimView>(nav); }},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TextEditorView>(nav); }},
|
||||
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<DebugMenuView>(nav); }},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FlashUtilityView>(nav); }},
|
||||
};
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
home(false);
|
||||
|
||||
auto it = appMap.find(name);
|
||||
if (it != appMap.end()) {
|
||||
push_view(std::unique_ptr<View>(it->second.viewFactory->produce(*this)));
|
||||
return true;
|
||||
for (const auto& app : appList) {
|
||||
if (app.id != nullptr && strcmp(app.id, name) == 0) {
|
||||
push_view(app.producer(*this));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -347,11 +326,11 @@ void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) {
|
||||
// Only show charging modal when transitioning to charging state
|
||||
nav_.display_modal(
|
||||
"CHARGING",
|
||||
"Screen on while charging?",
|
||||
"Enter deep sleep? \n \nExit: Press reset button. \n \nRX LED: Charging. \nTX LED: Charging error. \nLEDs OFF: Charge complete.",
|
||||
YESNO,
|
||||
[this](bool keep_screen_on) {
|
||||
if (!keep_screen_on) {
|
||||
EventDispatcher::set_display_sleep(true);
|
||||
[this](bool deepsleep) {
|
||||
if (deepsleep) {
|
||||
EventDispatcher::charge_deep_sleep(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -497,9 +476,8 @@ void SystemStatusView::on_camera() {
|
||||
auto error = png.create(path);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
std::vector<ColorRGB888> row(ui::screen_width);
|
||||
for (int i = 0; i < screen_height; i++) {
|
||||
std::vector<ColorRGB888> row(ui::screen_width);
|
||||
portapack::display.read_pixels({0, i, screen_width, 1}, row);
|
||||
png.write_scanline(row);
|
||||
}
|
||||
@@ -655,6 +633,7 @@ void NavigationView::pop(bool trigger_update) {
|
||||
|
||||
free_view();
|
||||
view_stack.pop_back();
|
||||
view_stack.shrink_to_fit();
|
||||
|
||||
// NB: These are executed _after_ the view has been
|
||||
// destroyed. The old view MUST NOT be referenced in
|
||||
@@ -778,10 +757,22 @@ void NavigationView::handle_autostart() {
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
const AppInfo* p_app = &app;
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, &app]() {
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0); //if i navigate away from any menu, turn off autoscan
|
||||
nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }},
|
||||
[&nav, p_app]() {
|
||||
NavigationView& local_nav = nav;
|
||||
if (p_app->menuLocation == HOME) {
|
||||
local_nav.store_last_menu_name(p_app->displayName);
|
||||
local_nav.set_last_menu_went_deeper(false);
|
||||
auto new_view = p_app->producer(local_nav);
|
||||
local_nav.push_view(std::move(new_view));
|
||||
} else {
|
||||
auto new_view = p_app->producer(local_nav);
|
||||
local_nav.pop();
|
||||
local_nav.set_last_menu_went_deeper(true);
|
||||
local_nav.push_view(std::move(new_view));
|
||||
}
|
||||
}},
|
||||
true);
|
||||
}
|
||||
};
|
||||
@@ -808,11 +799,11 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
return a.desired_position < b.desired_position;
|
||||
});
|
||||
|
||||
for (auto const& gridItem : externalItems) {
|
||||
for (auto & gridItem : externalItems) {
|
||||
if (gridItem.desired_position < 0) {
|
||||
grid.add_item(gridItem, true);
|
||||
grid.add_item(std::move(gridItem), true);
|
||||
} else {
|
||||
grid.insert_item(gridItem, gridItem.desired_position, true);
|
||||
grid.insert_item(std::move(gridItem), gridItem.desired_position, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -940,6 +931,27 @@ SystemView::SystemView(
|
||||
{{0, 0},
|
||||
{parent_rect.width(), status_view_height}});
|
||||
status_view.on_back = [this]() {
|
||||
if (this->navigation_view.view_stack_size() == 2) {
|
||||
const AppInfo* lastmenu = nullptr;
|
||||
const auto last_menu_name = this->navigation_view.get_last_menu_name();
|
||||
this->navigation_view.store_last_menu_name("");
|
||||
if (!last_menu_name.empty()) {
|
||||
for (const auto& app : NavigationView::appList) {
|
||||
if (app.displayName == last_menu_name) {
|
||||
lastmenu = &app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastmenu && this->navigation_view.get_last_menu_went_deeper()) {
|
||||
this->navigation_view.pop();
|
||||
this->navigation_view.push_view(lastmenu->producer(this->navigation_view));
|
||||
this->navigation_view.store_last_menu_name(lastmenu->displayName); // since we opened it again, store it again
|
||||
this->navigation_view.set_last_menu_went_deeper(false); // not yet
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->navigation_view.pop();
|
||||
};
|
||||
|
||||
@@ -956,6 +968,7 @@ SystemView::SystemView(
|
||||
navigation_view.on_view_changed = [this](const View& new_view) {
|
||||
if (!this->navigation_view.is_top()) {
|
||||
remove_child(&info_view);
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0);
|
||||
} else {
|
||||
add_child(&info_view);
|
||||
info_view.refresh();
|
||||
@@ -994,7 +1007,8 @@ void SystemView::toggle_overlay() {
|
||||
static uint8_t last_perf_counter_status = shared_memory.request_m4_performance_counter;
|
||||
switch (++overlay_active) {
|
||||
case 1:
|
||||
this->add_child(&this->overlay);
|
||||
overlay = std::make_unique<DfuMenu>(navigation_view);
|
||||
this->add_child(overlay.get());
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_performance_counter = 0;
|
||||
@@ -1002,13 +1016,16 @@ void SystemView::toggle_overlay() {
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
break;
|
||||
case 2:
|
||||
this->remove_child(&this->overlay);
|
||||
this->add_child(&this->overlay2);
|
||||
this->remove_child(overlay.get());
|
||||
overlay.reset();
|
||||
overlay2 = std::make_unique<DfuMenu2>(navigation_view);
|
||||
this->add_child(overlay2.get());
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 2;
|
||||
break;
|
||||
case 3:
|
||||
this->remove_child(&this->overlay2);
|
||||
this->remove_child(overlay2.get());
|
||||
overlay2.reset();
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = last_perf_counter_status;
|
||||
overlay_active = 0;
|
||||
@@ -1019,15 +1036,13 @@ void SystemView::toggle_overlay() {
|
||||
void SystemView::paint_overlay() {
|
||||
static bool last_paint_state = false;
|
||||
if (overlay_active) {
|
||||
// paint background only every other second
|
||||
if ((((chTimeNow() >> 10) & 0x01) == 0x01) == last_paint_state)
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
if (overlay_active == 1)
|
||||
this->overlay.set_dirty();
|
||||
else
|
||||
this->overlay2.set_dirty();
|
||||
if (overlay_active == 1 && overlay)
|
||||
overlay->set_dirty();
|
||||
else if (overlay_active == 2 && overlay2)
|
||||
overlay2->set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
#include "lfsr_random.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include "external_app.hpp"
|
||||
#include "view_factory.hpp"
|
||||
#include "battery.hpp"
|
||||
|
||||
// for incrementing fake date when RTC battery is dead
|
||||
@@ -60,6 +59,9 @@ using namespace sd_card;
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NavigationView;
|
||||
using ViewProducer = std::unique_ptr<View> (*)(NavigationView&);
|
||||
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile = true);
|
||||
|
||||
@@ -69,12 +71,7 @@ enum modal_t {
|
||||
ABORT
|
||||
};
|
||||
|
||||
class CstrCmp {
|
||||
public:
|
||||
bool operator()(const char* a, const char* b) const;
|
||||
};
|
||||
|
||||
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
|
||||
// Should only be used as part of the appList in NavigationView.
|
||||
class AppInfo {
|
||||
public:
|
||||
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
|
||||
@@ -82,7 +79,7 @@ class AppInfo {
|
||||
app_location_t menuLocation;
|
||||
Color iconColor;
|
||||
const Bitmap* icon;
|
||||
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
|
||||
ViewProducer producer;
|
||||
};
|
||||
|
||||
struct AppInfoConsole {
|
||||
@@ -137,15 +134,19 @@ class NavigationView : public View {
|
||||
bool set_on_pop(std::function<void()> on_pop);
|
||||
|
||||
// App list is used to preserve order, so the menu items in the menu grid can stay in place
|
||||
// App map is used to look up apps by id used by serial app start
|
||||
using AppMap = std::map<const char*, const AppInfo&, CstrCmp>;
|
||||
using AppList = std::vector<AppInfo>;
|
||||
static const AppMap appMap;
|
||||
static const AppList appList;
|
||||
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
|
||||
void handle_autostart();
|
||||
|
||||
void store_last_menu_name(const std::string& name) { last_menu_name_ = name; }
|
||||
std::string get_last_menu_name() const { return last_menu_name_; }
|
||||
size_t view_stack_size() const { return view_stack.size(); }
|
||||
|
||||
bool get_last_menu_went_deeper() { return last_menu_went_deeper; }
|
||||
void set_last_menu_went_deeper(bool went_deeper) { last_menu_went_deeper = went_deeper; }
|
||||
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
@@ -158,6 +159,8 @@ class NavigationView : public View {
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
std::string last_menu_name_{}; // this stores the last menu name, when we replace the menu with the app, we'll know, where to navigate back
|
||||
bool last_menu_went_deeper = false;
|
||||
};
|
||||
|
||||
/* Holds widgets and grows dynamically toward the left.
|
||||
@@ -451,8 +454,8 @@ class SystemView : public View {
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
NotificationView notification_view{navigation_view};
|
||||
DfuMenu overlay{navigation_view};
|
||||
DfuMenu2 overlay2{navigation_view};
|
||||
std::unique_ptr<DfuMenu> overlay{nullptr};
|
||||
std::unique_ptr<DfuMenu2> overlay2{nullptr};
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace ui {
|
||||
|
||||
NotificationEntryView::NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler)
|
||||
: entry_(entry), notifhandler_(notifhandler) {
|
||||
NotificationEntryView::NotificationEntryView(NotificationEntry entry, NotificationView* notifhandler)
|
||||
: entry_(std::move(entry)), notifhandler_(notifhandler) {
|
||||
add_children({&background, &border, &title_text, &message_text, &close_button});
|
||||
border.set_outline(true);
|
||||
if (entry_.icon != NOTIF_ICON_NONE) {
|
||||
@@ -94,13 +94,12 @@ void NotificationView::rearrange_notifications() {
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationView::add_notification(NotificationEntry& entry) {
|
||||
void NotificationView::add_notification(NotificationEntry entry) {
|
||||
if (notification_views_.size() >= max_notifications) {
|
||||
notification_views_.erase(notification_views_.begin());
|
||||
}
|
||||
|
||||
entry.id = ++curr_not_id;
|
||||
notification_views_.push_back(std::make_unique<NotificationEntryView>(entry, this));
|
||||
notification_views_.push_back(std::make_unique<NotificationEntryView>(std::move(entry), this));
|
||||
|
||||
rearrange_notifications();
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ class NotificationEntry {
|
||||
uint16_t id = 0;
|
||||
|
||||
NotificationEntry() = default;
|
||||
NotificationEntry(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000, uint16_t id = 0)
|
||||
: source_app(source_app), title(title), message(message), icon(icon), timeout(timeout), id(id) {}
|
||||
NotificationEntry(std::string source_app, std::string title, std::string message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000, uint16_t id = 0)
|
||||
: source_app(std::move(source_app)), title(std::move(title)), message(std::move(message)), icon(icon), timeout(timeout), id(id) {}
|
||||
|
||||
static NotificationEntry build(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000) {
|
||||
return NotificationEntry{source_app, title, message, icon, timeout, 0};
|
||||
static NotificationEntry build(std::string source_app, std::string title, std::string message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000) {
|
||||
return NotificationEntry{std::move(source_app), std::move(title), std::move(message), icon, timeout, 0};
|
||||
}
|
||||
|
||||
bool increase_time(uint16_t delta) {
|
||||
@@ -47,8 +47,7 @@ class NotificationEntry {
|
||||
|
||||
class NotificationEntryView : public View {
|
||||
public:
|
||||
NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler);
|
||||
|
||||
NotificationEntryView(NotificationEntry entry, NotificationView* notifhandler);
|
||||
NotificationEntryView(const NotificationEntryView&) = delete;
|
||||
NotificationEntryView& operator=(const NotificationEntryView&) = delete;
|
||||
|
||||
@@ -72,7 +71,7 @@ class NotificationView : public View {
|
||||
NotificationView(NavigationView& nav);
|
||||
~NotificationView();
|
||||
|
||||
void add_notification(NotificationEntry& entry);
|
||||
void add_notification(NotificationEntry entry);
|
||||
void remove_notification(uint16_t id);
|
||||
void open_notification(std::string app_name);
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ RecordView::RecordView(
|
||||
|
||||
RecordView::~RecordView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
if (is_active()) {
|
||||
capture_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void RecordView::focus() {
|
||||
|
||||
@@ -704,6 +704,7 @@ static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element)
|
||||
}
|
||||
|
||||
static void printAppInfo(BaseSequentialStream* chp, const ui::AppInfo& element) {
|
||||
if (element.id == nullptr) return;
|
||||
if (strlen(element.id) == 0) return;
|
||||
chprintf(chp, element.id);
|
||||
chprintf(chp, " ");
|
||||
@@ -740,9 +741,9 @@ static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
// TODO(u-foka): Somehow order static and dynamic app lists together
|
||||
for (auto& element : ui::NavigationView::appMap) { // Use the map as its ordered by id
|
||||
printAppInfo(chp, element.second);
|
||||
// todo U-foka : sort the list
|
||||
for (auto& element : ui::NavigationView::appList) {
|
||||
printAppInfo(chp, element);
|
||||
}
|
||||
ui::ExternalItemsMenuLoader::load_all_external_items_callback([chp](ui::AppInfoConsole& info) {
|
||||
printAppInfo(chp, info);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __VIEW_FACTORY_HPP__
|
||||
#define __VIEW_FACTORY_HPP__
|
||||
|
||||
#include "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <typename T>
|
||||
class ViewFactory : public ViewFactoryBase {
|
||||
public:
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const override {
|
||||
return std::unique_ptr<View>(new T(nav));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_HPP__
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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 "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
ViewFactoryBase::~ViewFactoryBase() {}
|
||||
|
||||
} // namespace ui
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __VIEW_FACTORY_BASE_HPP__
|
||||
#define __VIEW_FACTORY_BASE_HPP__
|
||||
|
||||
#include <memory>
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NavigationView;
|
||||
|
||||
class ViewFactoryBase {
|
||||
public:
|
||||
virtual ~ViewFactoryBase();
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_BASE_HPP__
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <libopencm3/lpc43xx/rgu.h>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
#include "delay.h"
|
||||
#include "string.h"
|
||||
|
||||
volatile bool usb_bulk_block_done = false;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1167,6 +1167,21 @@ typedef struct {
|
||||
__IO uint32_t AYRS;
|
||||
} LPC_RTC_Type;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ----- EVENT ROUTER -----
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
typedef struct {
|
||||
__IO uint32_t HILO; /* +0x000 */
|
||||
__IO uint32_t EDGE; /* +0x004 */
|
||||
__O uint32_t CLR_EN; /* +0x008 */
|
||||
__O uint32_t SET_EN; /* +0x00C */
|
||||
__I uint32_t STATUS; /* +0x010 */
|
||||
__I uint32_t ENABLE; /* +0x014 */
|
||||
__O uint32_t CLR_STAT; /* +0x018 */
|
||||
__O uint32_t SET_STAT; /* +0x01C */
|
||||
} LPC_EVENTROUTER_Type;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ----- USART0/2/3 -----
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -1466,6 +1481,7 @@ typedef struct {
|
||||
#define LPC_ADC1 ((LPC_ADCx_Type *) LPC_ADC1_BASE)
|
||||
#define LPC_GPIO ((LPC_GPIO_Type *) LPC_GPIO_BASE)
|
||||
#define LPC_SGPIO ((LPC_SGPIO_Type *) LPC_SGPIO_BASE)
|
||||
#define LPC_EVENTROUTER ((LPC_EVENTROUTER_Type *) LPC_EVENT_ROUTER_BASE)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -106,10 +106,12 @@ struct complex<int16_t> {
|
||||
} /* namespace std */
|
||||
|
||||
using complex8_t = std::complex<int8_t>;
|
||||
using complexu8_t = std::complex<uint8_t>;
|
||||
using complex16_t = std::complex<int16_t>;
|
||||
using complex32_t = std::complex<int32_t>;
|
||||
|
||||
static_assert(sizeof(complex8_t) == 2, "complex8_t size wrong");
|
||||
static_assert(sizeof(complexu8_t) == 2, "complexu8_t size wrong");
|
||||
static_assert(sizeof(complex16_t) == 4, "complex16_t size wrong");
|
||||
static_assert(sizeof(complex32_t) == 8, "complex32_t size wrong");
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ constexpr GPIO gpio_q_invert = gpio[GPIO0_13];
|
||||
constexpr GPIO gpio_vaa_disable = gpio[GPIO4_1]; // VAA disable (P8_1)
|
||||
constexpr GPIO gpio_1v2_enable = gpio[GPIO4_7]; // 1V2 enable (P8_7)
|
||||
constexpr GPIO gpio_3v3aux_disable = gpio[GPIO5_15]; // 3V3 aux disable (P6_7)
|
||||
|
||||
constexpr GPIO gpio_vbus_enable = gpio[GPIO8_4]; // VBUS_IN_EN P8_4 ->LOW
|
||||
constexpr GPIO gpio_vin_enable = gpio[GPIO8_5]; // VIN_IN_EN P8_5 ->HIGH
|
||||
// PRALINE RF path control
|
||||
constexpr GPIO gpio_tx_enable = gpio[GPIO3_4]; // TX enable (P6_5)
|
||||
// constexpr GPIO gpio_mix_enable_n = gpio[GPIO3_2]; // Mixer enable inverted (P6_3)
|
||||
|
||||
@@ -242,4 +242,14 @@ std::vector<uint8_t> I2cDev_PPmod::downloadStandaloneApp(uint32_t index, size_t
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool I2cDev_PPmod::send_poweroff_command() {
|
||||
if (isLocked()) return false; // device is busy
|
||||
Command cmd = Command::COMMAND_POWER_OFF;
|
||||
uint8_t tmp = 0; // result of the ack. 0 = no, 1 = ok
|
||||
if (!i2c_read((uint8_t*)&cmd, 2, &tmp, 1)) {
|
||||
return false;
|
||||
}
|
||||
return (tmp == 1);
|
||||
}
|
||||
|
||||
} // namespace i2cdev
|
||||
|
||||
@@ -59,7 +59,8 @@ class I2cDev_PPmod : public I2cDev {
|
||||
COMMAND_SHELL_PPTOMOD_DATA, // pp shell to esp. size not defined
|
||||
COMMAND_SHELL_MODTOPP_DATA_SIZE, // how many bytes the esp has to send to pp's shell
|
||||
COMMAND_SHELL_MODTOPP_DATA, // the actual bytes sent by esp. 1st byte's 1st bit is the "hasmore" flag, the remaining 7 bits are the size of the data. exactly 64 byte follows.
|
||||
|
||||
// poweroff command
|
||||
COMMAND_POWER_OFF, // requests power off from the esp, and after it needs a full power cycle to get it back again
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -92,6 +93,8 @@ class I2cDev_PPmod : public I2cDev {
|
||||
uint16_t get_shell_buffer_bytes();
|
||||
bool get_shell_get_buffer_data(uint8_t* buff, size_t len);
|
||||
|
||||
bool send_poweroff_command(); // sends the command to power off the device. Restarting the esp needs a full power cycle!
|
||||
|
||||
private:
|
||||
uint8_t self_timer = 0;
|
||||
uint64_t mask = 0; // feauture mask, that indicates what the device can do. this will determinate what we will query from the device
|
||||
|
||||
@@ -170,7 +170,7 @@ bool I2cDev::i2c_write(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t by
|
||||
if (bytes == 0) return false;
|
||||
// Create a new buffer to hold both reg and data
|
||||
uint8_t total_size = reg_size + bytes;
|
||||
uint8_t* buffer = new uint8_t[total_size];
|
||||
uint8_t buffer[total_size];
|
||||
// Copy the register data into the buffer
|
||||
if (reg_size > 0 && reg) {
|
||||
memcpy(buffer, reg, reg_size);
|
||||
@@ -179,8 +179,6 @@ bool I2cDev::i2c_write(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t by
|
||||
memcpy(buffer + reg_size, data, bytes);
|
||||
// Transmit the combined data
|
||||
bool result = i2cbus.transmit(addr, buffer, total_size, 150);
|
||||
// Clean up the dynamically allocated buffer
|
||||
delete[] buffer;
|
||||
if (!result)
|
||||
got_error();
|
||||
else
|
||||
|
||||
@@ -115,7 +115,9 @@ enum Pins {
|
||||
PA_1,
|
||||
PA_2,
|
||||
// Port 8 pins (PRALINE power control and LED4)
|
||||
P8_1,
|
||||
P8_1, // VAA_EN
|
||||
P8_4, // VBUS_IN_EN
|
||||
P8_5, // VIN_IN_EN
|
||||
P8_6,
|
||||
P8_7,
|
||||
// Port E pins (PRALINE transceiver control)
|
||||
@@ -227,6 +229,8 @@ constexpr Pin pins[]{
|
||||
[PA_2] = {0xA, 2},
|
||||
// Port 8 pins
|
||||
[P8_1] = {8, 1},
|
||||
[P8_4] = {8, 4},
|
||||
[P8_5] = {8, 5},
|
||||
[P8_6] = {8, 6},
|
||||
[P8_7] = {8, 7},
|
||||
// Port E pins (function 4 = GPIO)
|
||||
@@ -333,6 +337,8 @@ enum GPIOs {
|
||||
GPIO4_14,
|
||||
// Power control and LED4
|
||||
GPIO4_1,
|
||||
GPIO8_4,
|
||||
GPIO8_5,
|
||||
GPIO4_6,
|
||||
GPIO4_7,
|
||||
// RF path control
|
||||
@@ -445,6 +451,8 @@ constexpr GPIO gpio[] = {
|
||||
#ifdef PRALINE
|
||||
[GPIO4_14] = {pins[P9_2], 4, 14, 0}, // RFFC5072 mixer data
|
||||
[GPIO4_1] = {pins[P8_1], 4, 1, 0}, // VAA disable
|
||||
[GPIO8_4] = {pins[P8_4], 8, 4, 0}, // VBUS_IN_EN
|
||||
[GPIO8_5] = {pins[P8_5], 8, 5, 0}, // VIN_IN_EN
|
||||
[GPIO4_6] = {pins[P8_6], 4, 6, 0}, // LED4
|
||||
[GPIO4_7] = {pins[P8_7], 4, 7, 0}, // 1V2 enable
|
||||
[GPIO4_8] = {pins[PA_1], 4, 8, 0}, // LPF enable
|
||||
@@ -462,6 +470,8 @@ constexpr GPIO gpio[] = {
|
||||
// Placeholder entries for non-PRALINE builds (use P0_0 as dummy)
|
||||
[GPIO4_14] = {pins[P0_0], 4, 14, 0},
|
||||
[GPIO4_1] = {pins[P0_0], 4, 1, 0},
|
||||
[GPIO8_4] = {pins[P0_0], 8, 4, 0},
|
||||
[GPIO8_5] = {pins[P0_0], 8, 5, 0},
|
||||
[GPIO4_6] = {pins[P0_0], 4, 6, 0},
|
||||
[GPIO4_7] = {pins[P0_0], 4, 7, 0},
|
||||
[GPIO4_8] = {pins[P0_0], 4, 8, 0},
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
// to check battery mins and maxes in pmem, and validity
|
||||
#define BATT_18650_MIN_MAH 1000
|
||||
#define BATT_18650_MAX_MAH 5000
|
||||
#define BATT_18650_MAX_MAH 20000
|
||||
|
||||
using namespace modems;
|
||||
using namespace serializer;
|
||||
|
||||
@@ -393,27 +393,56 @@ void Text::getAccessibilityText(std::string& result) {
|
||||
void Text::getWidgetName(std::string& result) {
|
||||
result = "Text";
|
||||
}
|
||||
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
const int char_width = s.font.char_width();
|
||||
const int line_height = s.font.line_height();
|
||||
auto default_style = has_focus() ? style().invert() : style();
|
||||
painter.fill_rectangle(rect, default_style.background);
|
||||
const int char_width = default_style.font.char_width();
|
||||
const int line_height = default_style.font.line_height();
|
||||
if (char_width == 0 || line_height == 0) return;
|
||||
const size_t chars_per_line = rect.width() / char_width;
|
||||
if (chars_per_line == 0) return;
|
||||
size_t lines_capacity = rect.height() / line_height;
|
||||
if (lines_capacity == 0) lines_capacity = 1; // at least one line, even if it overflows vertically
|
||||
if (lines_capacity == 0) lines_capacity = 1;
|
||||
auto text_view = std::string_view{text};
|
||||
size_t current_offset = 0;
|
||||
// Track the active text color across line breaks
|
||||
Color active_color = default_style.foreground;
|
||||
for (size_t line_idx = 0; line_idx < lines_capacity; ++line_idx) {
|
||||
if (current_offset >= text_view.length()) break;
|
||||
size_t chunk_len = std::min(chars_per_line, text_view.length() - current_offset);
|
||||
size_t scan_offset = current_offset;
|
||||
size_t printable_count = 0;
|
||||
Color next_color = active_color; // This will hold the color state as we scan the current chunk
|
||||
// Scan forward to find chunk length AND track color changes
|
||||
while (scan_offset < text_view.length() && printable_count < chars_per_line) {
|
||||
if (text_view[scan_offset] == '\x1B') {
|
||||
scan_offset++;
|
||||
if (scan_offset < text_view.length()) {
|
||||
uint8_t color_idx = text_view[scan_offset];
|
||||
// Mirror the logic from Painter::draw_string
|
||||
if (color_idx < std::size(term_colors)) {
|
||||
next_color = term_colors[color_idx];
|
||||
} else {
|
||||
next_color = active_color;
|
||||
}
|
||||
scan_offset++;
|
||||
}
|
||||
} else {
|
||||
scan_offset++;
|
||||
printable_count++;
|
||||
}
|
||||
}
|
||||
size_t chunk_len = scan_offset - current_offset;
|
||||
// Create a temporary style for this line, initialized with the carried-over color
|
||||
Style line_style = {.font = default_style.font, .background = default_style.background, .foreground = active_color};
|
||||
painter.draw_string(
|
||||
rect.location() + Point(0, line_idx * line_height),
|
||||
s,
|
||||
line_style,
|
||||
text_view.substr(current_offset, chunk_len));
|
||||
current_offset += chunk_len;
|
||||
// Carry the final color state of this line into the start of the next line
|
||||
active_color = next_color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +732,7 @@ void Console::clear(bool clear_buffer = false) {
|
||||
pos = {0, 0};
|
||||
}
|
||||
|
||||
void Console::write(std::string message) {
|
||||
void Console::write(const std::string& message) {
|
||||
bool escape = false;
|
||||
|
||||
if (!hidden() && drawn()) {
|
||||
@@ -754,7 +783,7 @@ void Console::getWidgetName(std::string& result) {
|
||||
result = "Console";
|
||||
}
|
||||
|
||||
void Console::writeln(std::string message) {
|
||||
void Console::writeln(const std::string& message) {
|
||||
write(message + "\n");
|
||||
}
|
||||
|
||||
@@ -845,7 +874,7 @@ Checkbox::Checkbox(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void Checkbox::set_text(const std::string value) {
|
||||
void Checkbox::set_text(const std::string& value) {
|
||||
text_ = value;
|
||||
set_dirty();
|
||||
}
|
||||
@@ -977,7 +1006,7 @@ Button::Button(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void Button::set_text(const std::string value) {
|
||||
void Button::set_text(const std::string& value) {
|
||||
text_ = value;
|
||||
set_dirty();
|
||||
}
|
||||
@@ -1124,7 +1153,7 @@ ButtonWithEncoder::ButtonWithEncoder(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void ButtonWithEncoder::set_text(const std::string value) {
|
||||
void ButtonWithEncoder::set_text(const std::string& value) {
|
||||
text_ = value;
|
||||
set_dirty();
|
||||
}
|
||||
@@ -1296,7 +1325,7 @@ NewButton::NewButton(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void NewButton::set_text(const std::string value) {
|
||||
void NewButton::set_text(const std::string& value) {
|
||||
text_ = value;
|
||||
set_dirty();
|
||||
}
|
||||
@@ -1381,17 +1410,17 @@ void NewButton::paint(Painter& painter) {
|
||||
|
||||
if (!text_.empty()) {
|
||||
auto label_r = style.font.size_of(text_);
|
||||
std::string text_to_draw = text_;
|
||||
if (label_r.width() > r.width() - 2) {
|
||||
// Truncate text to fit
|
||||
size_t max_chars = (r.width() - 2) / style.font.char_width();
|
||||
text_to_draw = text_.substr(0, max_chars);
|
||||
label_r = style.font.size_of(text_to_draw);
|
||||
}
|
||||
if (bitmap_) {
|
||||
y += spacing;
|
||||
}
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_to_draw);
|
||||
if (label_r.width() > r.width() - 2) {
|
||||
size_t max_chars = (r.width() - 2) / style.font.char_width();
|
||||
std::string text_to_draw = text_.substr(0, max_chars);
|
||||
label_r = style.font.size_of(text_to_draw);
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_to_draw);
|
||||
} else {
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_);
|
||||
}
|
||||
}
|
||||
} else { // no valign
|
||||
if (bitmap_) {
|
||||
@@ -1406,15 +1435,14 @@ void NewButton::paint(Painter& painter) {
|
||||
|
||||
if (!text_.empty()) {
|
||||
auto label_r = style.font.size_of(text_);
|
||||
std::string text_to_draw = text_;
|
||||
if (label_r.width() > r.width() - 2) {
|
||||
// Truncate text to fit
|
||||
size_t max_chars = (r.width() - 2) / style.font.char_width();
|
||||
text_to_draw = text_.substr(0, max_chars);
|
||||
std::string text_to_draw = text_.substr(0, max_chars);
|
||||
label_r = style.font.size_of(text_to_draw);
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style, text_to_draw);
|
||||
} else {
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style, text_);
|
||||
}
|
||||
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style,
|
||||
text_to_draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +360,8 @@ class Console : public Widget {
|
||||
Console(Rect parent_rect);
|
||||
|
||||
void clear(bool clear_buffer);
|
||||
void write(std::string message);
|
||||
void writeln(std::string message);
|
||||
void write(const std::string& message);
|
||||
void writeln(const std::string& message);
|
||||
|
||||
void paint(Painter&) override;
|
||||
|
||||
@@ -401,7 +401,7 @@ class Checkbox : public Widget {
|
||||
Checkbox& operator=(const Checkbox&) = delete;
|
||||
Checkbox& operator=(Checkbox&&) = delete;
|
||||
|
||||
void set_text(const std::string value);
|
||||
void set_text(const std::string& value);
|
||||
bool set_value(const bool value);
|
||||
bool value() const;
|
||||
|
||||
@@ -439,7 +439,7 @@ class Button : public Widget {
|
||||
: Button{{}, {}} {
|
||||
}
|
||||
|
||||
void set_text(const std::string value);
|
||||
void set_text(const std::string& value);
|
||||
std::string text() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
@@ -477,7 +477,7 @@ class ButtonWithEncoder : public Widget {
|
||||
|
||||
std::function<void()> on_change{};
|
||||
|
||||
void set_text(const std::string value);
|
||||
void set_text(const std::string& value);
|
||||
int32_t get_encoder_delta();
|
||||
void set_encoder_delta(const int32_t delta);
|
||||
std::string text() const;
|
||||
@@ -516,7 +516,7 @@ class NewButton : public Widget {
|
||||
}
|
||||
|
||||
void set_bitmap(const Bitmap* bitmap);
|
||||
void set_text(const std::string value);
|
||||
void set_text(const std::string& value);
|
||||
void set_color(Color value);
|
||||
void set_bg_color(Color value);
|
||||
void set_vertical_center(bool value);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'apricity' colormap from cmyt
|
||||
0,89,105,175
|
||||
8,90,111,171
|
||||
16,91,116,169
|
||||
25,93,121,167
|
||||
33,94,127,166
|
||||
41,96,132,165
|
||||
49,97,137,164
|
||||
58,99,141,164
|
||||
66,101,147,164
|
||||
74,103,151,163
|
||||
82,106,156,163
|
||||
90,108,161,163
|
||||
99,111,166,162
|
||||
107,114,171,161
|
||||
115,118,175,160
|
||||
123,121,180,159
|
||||
132,125,185,157
|
||||
140,128,190,155
|
||||
148,132,195,152
|
||||
156,137,199,149
|
||||
165,142,205,144
|
||||
173,147,209,139
|
||||
181,153,214,134
|
||||
189,160,218,129
|
||||
197,170,223,122
|
||||
206,179,227,115
|
||||
214,188,230,108
|
||||
222,199,234,100
|
||||
230,211,237,91
|
||||
239,223,240,81
|
||||
247,234,243,70
|
||||
255,246,246,55
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'arbre' colormap from cmyt
|
||||
0,113,14,13
|
||||
8,117,22,34
|
||||
16,121,29,54
|
||||
25,125,35,73
|
||||
33,129,41,95
|
||||
41,133,45,115
|
||||
49,136,49,136
|
||||
58,139,52,158
|
||||
66,141,56,184
|
||||
74,141,60,208
|
||||
82,138,69,228
|
||||
90,130,82,241
|
||||
99,118,100,245
|
||||
107,105,116,241
|
||||
115,92,129,234
|
||||
123,82,141,225
|
||||
132,72,153,217
|
||||
140,65,162,209
|
||||
148,60,171,203
|
||||
156,54,179,196
|
||||
165,48,189,189
|
||||
173,43,197,182
|
||||
181,40,206,173
|
||||
189,45,214,162
|
||||
197,62,222,147
|
||||
206,84,228,131
|
||||
214,110,234,113
|
||||
222,139,238,93
|
||||
230,173,240,69
|
||||
239,203,241,50
|
||||
247,231,241,44
|
||||
255,254,242,69
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'cividis' colormap from matplotlib
|
||||
0,0,34,78
|
||||
8,0,40,91
|
||||
16,0,46,106
|
||||
25,5,51,113
|
||||
33,28,57,111
|
||||
41,41,63,110
|
||||
49,51,68,109
|
||||
58,60,74,108
|
||||
66,69,80,108
|
||||
74,77,85,108
|
||||
82,85,91,109
|
||||
90,92,97,110
|
||||
99,100,103,112
|
||||
107,107,109,114
|
||||
115,114,114,116
|
||||
123,120,120,119
|
||||
132,128,127,120
|
||||
140,136,133,120
|
||||
148,144,139,120
|
||||
156,151,145,119
|
||||
165,160,152,117
|
||||
173,168,158,115
|
||||
181,176,165,113
|
||||
189,185,171,109
|
||||
197,194,179,105
|
||||
206,203,185,101
|
||||
214,211,192,95
|
||||
222,220,200,89
|
||||
230,230,208,81
|
||||
239,239,215,72
|
||||
247,248,223,60
|
||||
255,254,232,56
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'dusk' colormap from cmyt
|
||||
0,6,3,5
|
||||
8,11,10,15
|
||||
16,15,18,27
|
||||
25,18,27,38
|
||||
33,16,36,52
|
||||
41,7,45,62
|
||||
49,17,53,64
|
||||
58,35,59,64
|
||||
66,50,65,66
|
||||
74,63,70,69
|
||||
82,74,75,72
|
||||
90,86,81,73
|
||||
99,101,85,73
|
||||
107,115,89,73
|
||||
115,130,92,72
|
||||
123,145,95,70
|
||||
132,163,97,68
|
||||
140,179,99,64
|
||||
148,195,100,60
|
||||
156,213,100,54
|
||||
165,231,101,42
|
||||
173,239,111,31
|
||||
181,240,126,49
|
||||
189,241,140,73
|
||||
197,241,155,98
|
||||
206,242,168,119
|
||||
214,242,180,140
|
||||
222,243,192,161
|
||||
230,244,206,184
|
||||
239,245,218,204
|
||||
247,246,230,223
|
||||
255,249,241,241
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'inferno' colormap from matplotlib
|
||||
0,0,0,4
|
||||
8,4,3,18
|
||||
16,11,7,36
|
||||
25,21,11,55
|
||||
33,35,12,76
|
||||
41,49,10,92
|
||||
49,62,9,102
|
||||
58,76,12,107
|
||||
66,90,17,110
|
||||
74,103,22,110
|
||||
82,116,26,110
|
||||
90,128,31,108
|
||||
99,143,36,105
|
||||
107,155,41,100
|
||||
115,168,46,95
|
||||
123,180,51,89
|
||||
132,193,58,80
|
||||
140,204,66,72
|
||||
148,215,75,63
|
||||
156,224,85,54
|
||||
165,233,97,43
|
||||
173,239,110,33
|
||||
181,245,123,23
|
||||
189,248,137,12
|
||||
197,251,153,6
|
||||
206,252,168,13
|
||||
214,251,184,29
|
||||
222,249,199,47
|
||||
230,245,217,73
|
||||
239,242,232,101
|
||||
247,243,245,134
|
||||
255,252,255,164
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'kelp' colormap from cmyt
|
||||
0,20,6,117
|
||||
8,32,14,122
|
||||
16,43,20,126
|
||||
25,54,26,130
|
||||
33,66,32,133
|
||||
41,76,38,135
|
||||
49,85,44,135
|
||||
58,94,50,134
|
||||
66,103,57,133
|
||||
74,111,64,132
|
||||
82,117,71,130
|
||||
90,124,78,129
|
||||
99,130,87,128
|
||||
107,135,95,128
|
||||
115,139,103,128
|
||||
123,143,111,129
|
||||
132,146,120,132
|
||||
140,150,128,135
|
||||
148,153,137,139
|
||||
156,156,145,142
|
||||
165,158,154,147
|
||||
173,161,163,152
|
||||
181,163,172,156
|
||||
189,165,181,159
|
||||
197,168,191,161
|
||||
206,174,199,159
|
||||
214,182,207,157
|
||||
222,193,214,153
|
||||
230,207,222,149
|
||||
239,220,229,144
|
||||
247,235,235,139
|
||||
255,250,242,133
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'magma' colormap from matplotlib
|
||||
0,0,0,4
|
||||
8,3,3,18
|
||||
16,10,8,34
|
||||
25,19,13,52
|
||||
33,30,17,73
|
||||
41,42,17,92
|
||||
49,56,16,108
|
||||
58,69,16,119
|
||||
66,84,19,125
|
||||
74,96,24,128
|
||||
82,109,29,129
|
||||
90,121,34,130
|
||||
99,136,39,129
|
||||
107,148,44,128
|
||||
115,161,48,126
|
||||
123,174,52,123
|
||||
132,189,57,119
|
||||
140,202,62,114
|
||||
148,214,69,108
|
||||
156,226,77,102
|
||||
165,236,88,96
|
||||
173,243,101,92
|
||||
181,248,116,92
|
||||
189,251,131,95
|
||||
197,253,148,103
|
||||
206,254,163,111
|
||||
214,254,178,122
|
||||
222,254,193,133
|
||||
230,254,209,148
|
||||
239,253,224,161
|
||||
247,252,238,176
|
||||
255,252,253,191
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'mako' colormap from seaborn
|
||||
0,11,4,5
|
||||
8,20,9,16
|
||||
16,28,16,28
|
||||
25,36,22,40
|
||||
33,44,28,54
|
||||
41,50,34,67
|
||||
49,55,40,81
|
||||
58,59,47,95
|
||||
66,63,54,111
|
||||
74,65,62,125
|
||||
82,64,70,138
|
||||
90,62,79,148
|
||||
99,58,89,154
|
||||
107,56,99,157
|
||||
115,54,108,160
|
||||
123,53,117,161
|
||||
132,52,127,164
|
||||
140,52,136,166
|
||||
148,52,145,168
|
||||
156,52,154,170
|
||||
165,54,164,171
|
||||
173,58,173,172
|
||||
181,63,182,173
|
||||
189,71,191,173
|
||||
197,83,201,173
|
||||
206,101,208,173
|
||||
214,124,214,175
|
||||
222,148,220,181
|
||||
230,171,226,190
|
||||
239,190,232,202
|
||||
247,206,238,215
|
||||
255,222,245,229
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'octarine' colormap from cmyt
|
||||
0,5,37,81
|
||||
8,8,45,75
|
||||
16,15,51,70
|
||||
25,20,57,67
|
||||
33,25,63,65
|
||||
41,29,69,63
|
||||
49,32,75,60
|
||||
58,38,80,55
|
||||
66,48,85,47
|
||||
74,61,89,40
|
||||
82,76,91,33
|
||||
90,92,93,27
|
||||
99,108,94,23
|
||||
107,123,94,21
|
||||
115,137,94,21
|
||||
123,152,93,21
|
||||
132,168,91,24
|
||||
140,182,89,27
|
||||
148,196,85,31
|
||||
156,211,81,37
|
||||
165,227,75,47
|
||||
173,238,71,63
|
||||
181,244,77,85
|
||||
189,244,89,108
|
||||
197,243,103,131
|
||||
206,241,115,151
|
||||
214,239,127,169
|
||||
222,236,138,186
|
||||
230,233,149,204
|
||||
239,232,159,220
|
||||
247,231,167,236
|
||||
255,238,172,250
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'plasma' colormap from matplotlib
|
||||
0,13,8,135
|
||||
8,34,6,144
|
||||
16,49,5,151
|
||||
25,63,4,156
|
||||
33,78,2,162
|
||||
41,91,1,165
|
||||
49,103,0,168
|
||||
58,116,1,168
|
||||
66,129,4,167
|
||||
74,141,11,165
|
||||
82,152,20,160
|
||||
90,162,29,154
|
||||
99,173,39,147
|
||||
107,182,48,139
|
||||
115,191,57,132
|
||||
123,199,66,124
|
||||
132,207,76,116
|
||||
140,214,85,109
|
||||
148,221,94,102
|
||||
156,227,104,95
|
||||
165,233,114,87
|
||||
173,239,124,81
|
||||
181,243,135,74
|
||||
189,247,145,67
|
||||
197,250,158,59
|
||||
206,252,169,52
|
||||
214,253,181,46
|
||||
222,253,194,41
|
||||
230,252,208,37
|
||||
239,249,221,37
|
||||
247,245,235,39
|
||||
255,240,249,33
|
||||
@@ -0,0 +1,33 @@
|
||||
# 'viridis' colormap from matplotlib
|
||||
0,68,1,84
|
||||
8,71,13,96
|
||||
16,72,24,106
|
||||
25,72,35,116
|
||||
33,71,46,124
|
||||
41,69,56,130
|
||||
49,66,65,134
|
||||
58,62,74,137
|
||||
66,58,84,140
|
||||
74,54,93,141
|
||||
82,50,101,142
|
||||
90,46,109,142
|
||||
99,43,117,142
|
||||
107,40,125,142
|
||||
115,37,132,142
|
||||
123,34,140,141
|
||||
132,31,148,140
|
||||
140,30,156,137
|
||||
148,32,163,134
|
||||
156,37,171,130
|
||||
165,46,179,124
|
||||
173,58,186,118
|
||||
181,72,193,110
|
||||
189,88,199,101
|
||||
197,108,205,90
|
||||
206,127,211,78
|
||||
214,147,215,65
|
||||
222,168,219,52
|
||||
230,192,223,37
|
||||
239,213,226,26
|
||||
247,234,229,26
|
||||
255,253,231,37
|
||||
Reference in New Issue
Block a user