From 084b88564b3dfb05378ec1cff53a74e14c744203 Mon Sep 17 00:00:00 2001 From: Mark Thompson <129641948+NotherNgineer@users.noreply.github.com> Date: Sun, 9 Mar 2025 16:03:41 -0500 Subject: [PATCH] Stopwatch external app (#2553) --- firmware/application/external/external.cmake | 21 ++-- firmware/application/external/external.ld | 48 +++++---- .../application/external/stopwatch/main.cpp | 83 ++++++++++++++ .../external/stopwatch/ui_stopwatch.cpp | 101 ++++++++++++++++++ .../external/stopwatch/ui_stopwatch.hpp | 83 ++++++++++++++ firmware/common/ui_widget.cpp | 101 +++++++++--------- firmware/common/ui_widget.hpp | 1 + firmware/graphics/stopwatch.png | Bin 0 -> 204 bytes 8 files changed, 361 insertions(+), 77 deletions(-) create mode 100644 firmware/application/external/stopwatch/main.cpp create mode 100644 firmware/application/external/stopwatch/ui_stopwatch.cpp create mode 100644 firmware/application/external/stopwatch/ui_stopwatch.hpp create mode 100644 firmware/graphics/stopwatch.png diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index d43492ad..7fe07491 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -117,7 +117,7 @@ set(EXTCPPSRC #shoppingcart_lock external/shoppingcart_lock/main.cpp - external/shoppingcart_lock/shoppingcart_lock.cpp + external/shoppingcart_lock/shoppingcart_lock.cpp #ookbrute external/ookbrute/main.cpp @@ -129,8 +129,8 @@ set(EXTCPPSRC #cvs_spam external/cvs_spam/main.cpp - external/cvs_spam/cvs_spam.cpp - + external/cvs_spam/cvs_spam.cpp + #flippertx external/flippertx/main.cpp external/flippertx/ui_flippertx.cpp @@ -142,15 +142,15 @@ set(EXTCPPSRC #mcu_temperature external/mcu_temperature/main.cpp external/mcu_temperature/mcu_temperature.cpp - + #fmradio external/fmradio/main.cpp external/fmradio/ui_fmradio.cpp - + #tuner external/tuner/main.cpp external/tuner/ui_tuner.cpp - + #metronome external/metronome/main.cpp external/metronome/ui_metronome.cpp @@ -162,8 +162,8 @@ set(EXTCPPSRC #hopper external/hopper/main.cpp external/hopper/ui_hopper.cpp - - # whip calculator + + # whip calculator external/antenna_length/main.cpp external/antenna_length/ui_whipcalc.cpp @@ -178,6 +178,10 @@ set(EXTCPPSRC # playlist editor external/playlist_editor/main.cpp external/playlist_editor/ui_playlist_editor.cpp + + #stopwatch + external/stopwatch/main.cpp + external/stopwatch/ui_stopwatch.cpp ) set(EXTAPPLIST @@ -224,4 +228,5 @@ set(EXTAPPLIST view_wav sd_wipe playlist_editor + stopwatch ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index a341f60b..baf2266c 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -48,24 +48,25 @@ MEMORY ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k - ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k - ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k - ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k - ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k - ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k - ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k - ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k - ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k + ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k + ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k + ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k + ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k + ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k + ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k + ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k + ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k - ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k - ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k - ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k - ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k - ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k - ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k - ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k + ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k + ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k + ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k + ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k + ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k + ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k + ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k ram_external_app_breakout(rwx) : org = 0xADDA0000, len = 32k - ram_external_app_snake(rwx) : org = 0xADDB0000, len = 32k + ram_external_app_snake(rwx) : org = 0xADDB0000, len = 32k + ram_external_app_stopwatch(rwx) : org = 0xADDC0000, len = 32k } SECTIONS @@ -158,13 +159,13 @@ SECTIONS { KEEP(*(.external_app.app_breakout.application_information)); *(*ui*external_app*breakout*); - } > ram_external_app_breakout + } > ram_external_app_breakout .external_app_snake : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_snake.application_information)); *(*ui*external_app*snake*); - } > ram_external_app_snake + } > ram_external_app_snake .external_app_extsensors : ALIGN(4) SUBALIGN(4) { @@ -208,7 +209,6 @@ SECTIONS *(*ui*external_app*adsbtx*); } > ram_external_app_adsbtx - .external_app_morse_tx : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_morse_tx.application_information)); @@ -274,13 +274,13 @@ SECTIONS KEEP(*(.external_app.app_mcu_temperature.application_information)); *(*ui*external_app*mcu_temperature*); } > ram_external_app_mcu_temperature - + .external_app_fmradio : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_fmradio.application_information)); *(*ui*external_app*fmradio*); } > ram_external_app_fmradio - + .external_app_tuner : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_tuner.application_information)); @@ -328,4 +328,10 @@ SECTIONS KEEP(*(.external_app.app_playlist_editor.application_information)); *(*ui*external_app*playlist_editor*); } > ram_external_app_playlist_editor + + .external_app_stopwatch : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_stopwatch.application_information)); + *(*ui*external_app*stopwatch*); + } > ram_external_app_stopwatch } diff --git a/firmware/application/external/stopwatch/main.cpp b/firmware/application/external/stopwatch/main.cpp new file mode 100644 index 00000000..1bfea622 --- /dev/null +++ b/firmware/application/external/stopwatch/main.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2025 Mark Thompson + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "ui_stopwatch.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::stopwatch { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::stopwatch + +extern "C" { + +__attribute__((section(".external_app.app_stopwatch.application_information"), used)) application_information_t _application_information_stopwatch = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::stopwatch::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "Stopwatch", + /*.bitmap_data = */ { + 0x00, + 0x00, + 0xC0, + 0x01, + 0x80, + 0x00, + 0x80, + 0x20, + 0x60, + 0x13, + 0x10, + 0x0C, + 0x88, + 0x08, + 0x84, + 0x10, + 0x84, + 0x10, + 0xC2, + 0x21, + 0x84, + 0x10, + 0x04, + 0x10, + 0x08, + 0x08, + 0x10, + 0x04, + 0x60, + 0x03, + 0x80, + 0x00, + }, + /*.icon_color = */ ui::Color::cyan().v, + /*.menu_location = */ app_location_t::UTILITIES, + /*.desired_menu_position = */ -1, + + /*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} \ No newline at end of file diff --git a/firmware/application/external/stopwatch/ui_stopwatch.cpp b/firmware/application/external/stopwatch/ui_stopwatch.cpp new file mode 100644 index 00000000..68e70c37 --- /dev/null +++ b/firmware/application/external/stopwatch/ui_stopwatch.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2025 Mark Thompson + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_stopwatch.hpp" +#include "portapack.hpp" +#include "ch.h" + +using namespace portapack; + +namespace ui::external_app::stopwatch { + +StopwatchView::StopwatchView(NavigationView& nav) { + add_children({ + &labels, + &button_run_stop, + &button_reset_lap, + &button_done, + &big_display, + &lap_display, + }); + + button_run_stop.on_select = [this](Button&) { + if (running) + stop(); + else + run(); + }; + + button_reset_lap.on_select = [this](Button&) { + if (running) + lap(); + else + reset(); + }; + + button_done.on_select = [&nav](Button&) { + nav.pop(); + }; +} + +void StopwatchView::focus() { + button_run_stop.focus(); +} + +void StopwatchView::run() { + running = true; + start_time = chTimeNow() - previously_elapsed; + button_run_stop.set_text("STOP"); + button_reset_lap.set_text("LAP"); +} + +void StopwatchView::stop() { + running = false; + end_time = chTimeNow(); + previously_elapsed = end_time - start_time; + button_run_stop.set_text("START"); + button_reset_lap.set_text("RESET"); +} + +void StopwatchView::reset() { + lap_time = end_time = start_time = previously_elapsed = 0; + big_display.set(0); + lap_display.set(0); +} + +void StopwatchView::lap() { + lap_time = chTimeNow(); + lap_display.set((lap_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget +} + +void StopwatchView::paint(Painter& painter) { + (void)painter; + if (running) { + end_time = chTimeNow(); + big_display.set((end_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget + } +} + +void StopwatchView::frame_sync() { + set_dirty(); +} + +} // namespace ui::external_app::stopwatch \ No newline at end of file diff --git a/firmware/application/external/stopwatch/ui_stopwatch.hpp b/firmware/application/external/stopwatch/ui_stopwatch.hpp new file mode 100644 index 00000000..2cc7c9f7 --- /dev/null +++ b/firmware/application/external/stopwatch/ui_stopwatch.hpp @@ -0,0 +1,83 @@ +/* + * Copyright 2025 Mark Thompson + * + * 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 __UI_STOPWATCH_H__ +#define __UI_STOPWATCH_H__ + +#include "ui_navigation.hpp" + +namespace ui::external_app::stopwatch { + +class StopwatchView : public View { + public: + StopwatchView(NavigationView& nav); + void focus() override; + void paint(Painter& painter) override; + void frame_sync(); + std::string title() const override { return "Stopwatch"; }; + + private: + void run(); + void stop(); + void reset(); + void lap(); + + bool running{false}; + long long start_time{0}; + long long end_time{0}; + long long lap_time{0}; + long long previously_elapsed{0}; + + Labels labels{ + {{0 * 8, 1 * 16}, "TOTAL:", Theme::getInstance()->fg_light->foreground}, + {{0 * 8, 7 * 16}, "LAP:", Theme::getInstance()->fg_light->foreground}, + }; + + BigFrequency big_display{ + {4, 2 * 16 + 4, 28 * 8, 52}, + 0}; + + BigFrequency lap_display{ + {4, 8 * 16 + 4, 28 * 8, 52}, + 0}; + + Button button_run_stop{ + {72, 210, 96, 24}, + "START"}; + + Button button_reset_lap{ + {72, 240, 96, 24}, + "RESET"}; + + Button button_done{ + {72, 270, 96, 24}, + "EXIT"}; + + MessageHandlerRegistration message_handler_frame_sync{ + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->frame_sync(); + }}; +}; + +} // namespace ui::external_app::stopwatch + +#endif /*__UI_STOPWATCH_H__*/ \ No newline at end of file diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 357192bc..31a3a053 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -528,58 +528,63 @@ void BigFrequency::paint(Painter& painter) { Point digit_pos; ui::Color segment_color; - const auto rect = screen_rect(); + if (_frequency != _previous_frequency) { + _previous_frequency = _frequency; - // Erase - painter.fill_rectangle( - {{0, rect.location().y()}, {screen_width, 52}}, - Theme::getInstance()->bg_darkest->background); + rf::Frequency frequency{_frequency}; + const auto rect = screen_rect(); - // Prepare digits - if (!_frequency) { - digits.fill(10); // ----.--- - digit_pos = {0, rect.location().y()}; - } else { - _frequency /= 1000; // GMMM.KKK(uuu) + // Erase + painter.fill_rectangle( + {{0, rect.location().y()}, {screen_width, 52}}, + Theme::getInstance()->bg_darkest->background); - for (i = 0; i < 7; i++) { - digits[6 - i] = _frequency % 10; - _frequency /= 10; - } - - // Remove leading zeros - for (i = 0; i < 3; i++) { - if (!digits[i]) - digits[i] = 16; // "Don't draw" code - else - break; - } - - digit_pos = {(Coord)(240 - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()}; - } - - segment_color = style().foreground; - - // Draw - for (i = 0; i < 7; i++) { - digit = digits[i]; - - if (digit < 16) { - digit_def = segment_font[(uint8_t)digit]; - - for (size_t s = 0; s < 7; s++) { - if (digit_def & 1) - painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color); - digit_def >>= 1; - } - } - - if (i == 3) { - // Dot - painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color); - digit_pos += {(digit_width + 8), 0}; + // Prepare digits + if (!frequency) { + digits.fill(10); // ----.--- + digit_pos = {0, rect.location().y()}; } else { - digit_pos += {digit_width, 0}; + frequency /= 1000; // GMMM.KKK(uuu) + + for (i = 0; i < 7; i++) { + digits[6 - i] = frequency % 10; + frequency /= 10; + } + + // Remove leading zeros + for (i = 0; i < 3; i++) { + if (!digits[i]) + digits[i] = 16; // "Don't draw" code + else + break; + } + + digit_pos = {(Coord)(240 - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()}; + } + + segment_color = style().foreground; + + // Draw + for (i = 0; i < 7; i++) { + digit = digits[i]; + + if (digit < 16) { + digit_def = segment_font[(uint8_t)digit]; + + for (size_t s = 0; s < 7; s++) { + if (digit_def & 1) + painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color); + digit_def >>= 1; + } + } + + if (i == 3) { + // Dot + painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color); + digit_pos += {(digit_width + 8), 0}; + } else { + digit_pos += {digit_width, 0}; + } } } } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 606c0243..dfda3edc 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -291,6 +291,7 @@ class BigFrequency : public Widget { private: rf::Frequency _frequency; + rf::Frequency _previous_frequency{~0LL}; static constexpr Dim digit_width = 32; diff --git a/firmware/graphics/stopwatch.png b/firmware/graphics/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9ec38f70d9cbfcb57c39cd02ee6cdcff46176e GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9F5M?jcysy3fAP_WL^ z#WBR9cWIC(-w^{Iqp<(~pMFWE>Poay5bj>ttqR)KC>ot*h%GkxrId$hN5{&TeYUKHf+J5g`eZ}u#+FpIY- z!Y_g}cCCtT@;tKl(qm=ONY>c7VGo|1+QGP9%18O`mzu>umoRv``njxgN@xNAhs;Tw literal 0 HcmV?d00001