diff --git a/.dockerignore b/.dockerignore
index c795b054e..8b83c831c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1 +1,2 @@
-build
\ No newline at end of file
+*
+!firmware/tools/docker-entrypoint.sh
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml
index 9b8f418da..7bd5436bf 100644
--- a/.github/ISSUE_TEMPLATE/01_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml
@@ -8,6 +8,12 @@ body:
value: |
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
+ > [!NOTE]
+ For hardware related issue, please open as a hardware issue instead.
+ For feature requests, please open as a feature request instead.
+ If you have questions, please join our Discord server to ask.
+ For your information, they are options you can choose when you open an issue.
+ Unrelated issues will be closed without notice.
- type: textarea
id: description
attributes:
diff --git a/.github/ISSUE_TEMPLATE/04_hardware_related.yml b/.github/ISSUE_TEMPLATE/04_hardware_related.yml
new file mode 100644
index 000000000..ebd51287b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/04_hardware_related.yml
@@ -0,0 +1,38 @@
+name: Hardware / QC / QA / selling
+description: Issues that related with hardware, quality control, quality assurance, selling, etc.
+labels:
+ - question
+ - hardware problem
+body:
+- type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to fill out an issue.
+
+ We would like to inform you that we can't provide much help regarding hardware/QC/QA/selling issues because we are not controlling various vendors/manufacturers, and also we are not profiting from you buying your device.
+
+ You are still welcome to ask, but please be aware that for this kind of issue, ask the seller would be the best option.
+- type: textarea
+ id: description
+ attributes:
+ label: Describe the problem
+ description: "A clear and concise description of what the problem is."
+ validations:
+ required: true
+- type: textarea
+ id: hardware-info
+ attributes:
+ label: Hardware information
+ description: "Please provide the hardware information here."
+ placeholder: |
+ 1. HackrfRF revision
+ 2. Portapack revision
+ 3. (if it's audio/mic/display/touchscreen/buttons related) audio chip/ CPLD/ screen info...
+ 4. (if hardware is visually damaged) please provide a photo
+ validations:
+ required: true
+- type: textarea
+ id: anything-else
+ attributes:
+ label: Anything else?
+ description: Let us know if you have anything else to share.
diff --git a/.github/workflows/hardware_issue_warning.yml b/.github/workflows/hardware_issue_warning.yml
new file mode 100644
index 000000000..7099730ce
--- /dev/null
+++ b/.github/workflows/hardware_issue_warning.yml
@@ -0,0 +1,20 @@
+name: Hardware Issue Warning
+
+on:
+ issues:
+ types: [labeled]
+
+jobs:
+ add-comment:
+ if: github.event.label.name == 'hardware problem'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: Add warning comment
+ uses: peter-evans/create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ > [!IMPORTANT]
+ If you are a seller/vendor and your customer has directed you to this page as a reference/evidence of a "broken device" or similar issue, please be aware that we are not providing any "official guarantee of a faulty device." We are only offering suggestions based on what the submitter has stated. We do not guarantee that the device is broken or anything else.
\ No newline at end of file
diff --git a/.github/workflows/past_version.txt b/.github/workflows/past_version.txt
index f3b15f3f8..1defe531b 100644
--- a/.github/workflows/past_version.txt
+++ b/.github/workflows/past_version.txt
@@ -1 +1 @@
-v2.0.2
+v2.1.0
diff --git a/.github/workflows/version.txt b/.github/workflows/version.txt
index 1defe531b..a4b6ac3de 100644
--- a/.github/workflows/version.txt
+++ b/.github/workflows/version.txt
@@ -1 +1 @@
-v2.1.0
+v2.2.0
diff --git a/.gitignore b/.gitignore
index d7ab86ca3..559d5257f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,6 +61,7 @@ cmake-build-debug/
*.sublime-workspace
.vscode
.idea
+*.swp
# VSCodium extensions
.history
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3aff31c87..0fbd8a82f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..415e5c8e6
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+https://github.com/portapack-mayhem/mayhem-firmware/issues/new/.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..07a6de6d1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+Please see [How-to-collaborate](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate)
diff --git a/README.md b/README.md
index 367aa36e9..f53ba77f8 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,13 @@
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
# PortaPack Mayhem
-[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
+[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
-[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
+[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
+[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
+[
]([https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h1r1r2))
# What is this?
@@ -26,11 +28,11 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
-:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/TUOLYI) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
+:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
-:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
+:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..3fd5e0ced
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1 @@
+Please check [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/)
diff --git a/dockerfile-nogit b/dockerfile-nogit
index 916e510c7..79801a39e 100644
--- a/dockerfile-nogit
+++ b/dockerfile-nogit
@@ -3,7 +3,7 @@ FROM ubuntu:noble
# Set location to download ARM toolkit from.
# This will need to be changed over time or replaced with a static link to the latest release.
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=108bd959-44bd-4619-9c19-26187abf5225&la=en&hash=E788CE92E5DFD64B2A8C246BBA91A249CB8E2D2D \
- PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
+ PATH=$PATH:/opt/build/armbin/bin
#Create volume /havoc/bin for compiled firmware binaries
VOLUME /havoc
@@ -15,8 +15,8 @@ RUN apt-get update \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
+ENV LANG=C.UTF-8
+ENV LC_ALL=C.UTF-8
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
diff --git a/dockerize.sh b/dockerize.sh
new file mode 100755
index 000000000..9f25d558e
--- /dev/null
+++ b/dockerize.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+#################################################
+# This script aids building mayhem inside docker
+#
+# Basic usage:
+# - Build dev container: ./dockerize.sh build
+# - Build mayhem: ./dockerize.sh
+#
+# The image will be automatically build if it
+# does not exist, but if the dockerfile changes
+# it need to be rebuilt manually.
+#
+# Advanced parameters:
+# - Get a shell inside the build image to
+# inspect problems: ./dockerize.sh shell
+# - Give additional parameters to the container:
+# ./dockerize.sh -j10
+# ./dockerize.sh ninja -j10
+# - Use a different dockerfile:
+# ./dockerize.sh build dockerfile-other
+# - Use a different cpu architecture:
+# ./dockerize.sh build dockerfile-nogit-arm arm64
+#
+# Environment variables:
+# - It is possible to override the default image
+# name that is being used to build and run the
+# build container using an environment variable.
+# The default is 'portapack-dev'
+# Override by setting the following environment
+# variable: MAYHEM_DEV_DOCKER_IMAGE
+#
+
+#
+# Copyright (C) 2024 u-foka
+#
+# 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.
+#
+
+set -e # exit immediatelly on any failure
+
+DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+
+IMAGE="${MAYHEM_DEV_DOCKER_IMAGE:-portapack-dev}"
+
+build_image() {
+ DOCKERFILE=${1:-dockerfile-nogit}
+ PLATFORM=${2:-amd64}
+ docker build --platform "linux/${PLATFORM}" -t "${IMAGE}" -f "${DOCKERFILE}" .
+}
+
+start_docker() {
+ if [ -z "$(docker images -q ${IMAGE} 2> /dev/null)" ]; then
+ build_image
+ fi
+
+ exec docker run -v "${DIR}:/havoc" -u "$(id -u):$(id -g)" -ti --rm "${IMAGE}" "$@"
+}
+
+if [ "$1" = 'shell' ]; then # open a shell into the container
+ start_docker "bash -li"
+elif [ "$1" = 'build' ]; then # build the default (or specified) target with ninja
+ shift # remove the first item from $@ as we consumed it, we can then pass the rest on to make
+ build_image "$@"
+ exit $?
+fi
+
+start_docker "$@"
diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt
index bc613db18..161be343b 100644
--- a/firmware/CMakeLists.txt
+++ b/firmware/CMakeLists.txt
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.16)
project(firmware)
diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt
index 8b4ce16c9..65b3bdf34 100644
--- a/firmware/application/CMakeLists.txt
+++ b/firmware/application/CMakeLists.txt
@@ -191,7 +191,7 @@ set(CPPSRC
clock_manager.cpp
core_control.cpp
database.cpp
- de_bruijn.cpp
+ gradient.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
@@ -294,7 +294,6 @@ set(CPPSRC
apps/ui_flash_utility.cpp
apps/ui_freqman.cpp
apps/ui_iq_trim.cpp
- apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
@@ -303,9 +302,7 @@ set(CPPSRC
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
- apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
- apps/ui_sd_wipe.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
@@ -317,9 +314,7 @@ set(CPPSRC
apps/ui_text_editor.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
- apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
- apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
@@ -377,7 +372,7 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
hw
apps
protocols
- bitmaps
+ bmps
)
#
diff --git a/firmware/application/app_settings.cpp b/firmware/application/app_settings.cpp
index 505ed456c..acf93b0ff 100644
--- a/firmware/application/app_settings.cpp
+++ b/firmware/application/app_settings.cpp
@@ -188,6 +188,7 @@ void copy_to_radio_model(const AppSettings& settings) {
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
+ settings.wfmam_config_index,
settings.squelch);
}
@@ -218,6 +219,7 @@ void copy_from_radio_model(AppSettings& settings) {
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
+ settings.wfmam_config_index = receiver_model.wfmam_configuration();
}
settings.step = receiver_model.frequency_step();
@@ -271,6 +273,7 @@ SettingsManager::SettingsManager(
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
+ bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
diff --git a/firmware/application/app_settings.hpp b/firmware/application/app_settings.hpp
index dd83c405e..d0c30d5a3 100644
--- a/firmware/application/app_settings.hpp
+++ b/firmware/application/app_settings.hpp
@@ -139,6 +139,7 @@ struct AppSettings {
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
+ uint8_t wfmam_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
diff --git a/firmware/application/apps/ais_app.cpp b/firmware/application/apps/ais_app.cpp
index 3e28fb4e8..07f2045cb 100644
--- a/firmware/application/apps/ais_app.cpp
+++ b/firmware/application/apps/ais_app.cpp
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
- GeoPos::spd_unit::NONE,
+ GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
- geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
+ geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
+}
+
+bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
+ if (geomap_view && send_updates) {
+ GeoMarker marker{};
+ marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
+ marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
+ marker.angle = entry.last_position.true_heading;
+ marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
+ auto markerStored = geomap_view->store_marker(marker);
+ return markerStored == MARKER_STORED;
+ }
+ return false;
+}
+void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
+ if (geomap_view && send_updates) {
+ geomap_view->clear_markers();
+ for (const auto& entry : entries) {
+ // if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
+ add_map_marker(entry);
+ // }
+ }
+ update_position(); // to update view
+ }
}
void AISRecentEntryDetailView::focus() {
@@ -415,14 +439,27 @@ AISAppView::AISAppView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
+
+ signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
+ on_tick_second();
+ };
}
AISAppView::~AISAppView() {
+ rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
+void AISAppView::on_tick_second() {
+ ++timer_seconds;
+ if (timer_seconds % 10 == 0) {
+ if (recent_entry_detail_view.hidden()) return;
+ recent_entry_detail_view.update_map_markers(recent);
+ }
+}
+
void AISAppView::focus() {
options_channel.focus();
}
@@ -461,6 +498,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.hidden(false);
recent_entry_detail_view.set_entry(entry);
recent_entry_detail_view.focus();
+ recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
diff --git a/firmware/application/apps/ais_app.hpp b/firmware/application/apps/ais_app.hpp
index 17aa700d5..20bb3a67c 100644
--- a/firmware/application/apps/ais_app.hpp
+++ b/firmware/application/apps/ais_app.hpp
@@ -125,10 +125,14 @@ class AISRecentEntryDetailView : public View {
void update_position();
void focus() override;
void paint(Painter&) override;
+ bool add_map_marker(const AISRecentEntry& entry);
+ void update_map_markers(AISRecentEntries& entries);
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
+ GeoMapView* get_geomap_view() { return geomap_view; }
+
private:
AISRecentEntry entry_{};
@@ -210,11 +214,13 @@ class AISAppView : public View {
};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
+ SignalToken signal_token_tick_second{};
+ uint8_t timer_seconds = 0;
MessageHandlerRegistration message_handler_packet{
Message::ID::AISPacket,
@@ -229,6 +235,7 @@ class AISAppView : public View {
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
+ void on_tick_second();
};
} /* namespace ui */
diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp
index 4726f44b5..a5c51f4a5 100644
--- a/firmware/application/apps/analog_audio_app.cpp
+++ b/firmware/application/apps/analog_audio_app.cpp
@@ -40,6 +40,7 @@ namespace ui {
/* AMOptionsView *********************************************************/
AMOptionsView::AMOptionsView(
+ AnalogAudioView* view,
Rect parent_rect,
const Style* style)
: View{parent_rect} {
@@ -48,12 +49,23 @@ AMOptionsView::AMOptionsView(
add_children({
&label_config,
&options_config,
+ &zoom_config,
});
- freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
- options_config.set_by_value(receiver_model.am_configuration());
- options_config.on_change = [this](size_t, OptionsField::value_t n) {
- receiver_model.set_am_configuration(n);
+ zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) { // n , has two option values. when GUI =zoom+1 => (0), when GUI=zoom+2 (6)
+ receiver_model.set_am_configuration(view->get_previous_AM_mode_option() + n); // n (0 or 6)
+ view->set_zoom_factor(AM_MODULATION, n);
+ view->set_previous_zoom_option(n);
+ };
+
+ // restore zoom selection
+ zoom_config.set_by_value(view->get_zoom_factor(AM_MODULATION));
+
+ freqman_set_bandwidth_option(AM_MODULATION, options_config); // freqman.cpp to the options_config, only allowing 5 modes freqman_bandwidths[AM] {"DSB 9k", 0}, {"DSB 6k", 1}, {"USB+3k", 2}, {"LSB-3k", 3}, {"CW", 4},
+ options_config.set_by_value(receiver_model.am_configuration() - view->get_previous_zoom_option()); // restore AM GUI option mode , AM FIR index filters (0..11) values , am_configs has 12 fir index elements.
+ options_config.on_change = [this, view](size_t, OptionsField::value_t n) {
+ receiver_model.set_am_configuration(n + view->get_previous_zoom_option()); // we select proper FIR AM filter (0..11), = 0..4 GUI AM modes + offset +6 (if zoom+2)
+ view->set_previous_AM_mode_option(n); // (0..4) allowing 5 AM modes (DSB9K, DSB6K, USB,LSB, CW)
};
}
@@ -102,6 +114,54 @@ WFMOptionsView::WFMOptionsView(
};
}
+/* WFMAMAptOptionsView *******************************************************/
+
+WFMAMAptOptionsView::WFMAMAptOptionsView(
+ Rect parent_rect,
+ const Style* style)
+ : View{parent_rect} {
+ set_style(style);
+
+ add_children({
+ &label_config,
+ &options_config,
+ });
+
+ freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
+ options_config.set_by_value(receiver_model.wfmam_configuration());
+ options_config.on_change = [this](size_t, OptionsField::value_t n) {
+ receiver_model.set_wfmam_configuration(n);
+ };
+}
+
+/* AMFMAptOptionsView *********************************************************/
+
+AMFMAptOptionsView::AMFMAptOptionsView(
+ AnalogAudioView* view,
+ Rect parent_rect,
+ const Style* style)
+ : View{parent_rect} {
+ set_style(style);
+
+ add_children({
+ &label_config,
+ &options_config,
+ &zoom_config,
+ });
+
+ freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
+ receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
+ options_config.set_by_value(receiver_model.amfm_configuration());
+
+ zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) {
+ receiver_model.set_amfm_configuration(5 + n);
+ view->set_zoom_factor(AMFM_MODULATION, n);
+ };
+
+ // restore zoom selection
+ zoom_config.set_by_value(view->get_zoom_factor(AMFM_MODULATION));
+}
+
/* SPECOptionsView *******************************************************/
SPECOptionsView::SPECOptionsView(
@@ -175,8 +235,9 @@ AnalogAudioView::AnalogAudioView(
};
auto modulation = receiver_model.modulation();
+
// This app doesn't handle "Capture" mode.
- if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
+ if (modulation == ReceiverModel::Mode::Capture)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
options_modulation.set_by_value(toUType(modulation));
@@ -226,11 +287,42 @@ void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
receiver_model.set_baseband_bandwidth(bw / 2);
}
-uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
+uint8_t AnalogAudioView::get_zoom_factor(uint8_t mode) { // define accessor functions inside AnalogAudioView to read zoom value
+ if (mode == AM_MODULATION)
+ return zoom_factor_am;
+ else if (mode == AMFM_MODULATION)
+ return zoom_factor_amfm;
+ return 0; // default if unsupported mode
+}
+
+void AnalogAudioView::set_zoom_factor(uint8_t mode, uint8_t zoom) { // define accessor functions inside AnalogAudioView to write zoom value
+ if (mode == AM_MODULATION)
+ zoom_factor_am = zoom;
+ else if (mode == AMFM_MODULATION)
+ zoom_factor_amfm = zoom;
+}
+
+uint8_t AnalogAudioView::get_previous_AM_mode_option() {
+ return previous_AM_mode_option;
+}
+
+void AnalogAudioView::set_previous_AM_mode_option(uint8_t mode) {
+ previous_AM_mode_option = mode;
+}
+
+uint8_t AnalogAudioView::get_previous_zoom_option() {
+ return previous_zoom;
+}
+
+void AnalogAudioView::set_previous_zoom_option(uint8_t zoom) {
+ previous_zoom = zoom;
+}
+
+uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read iq_phase_calibration_value
return iq_phase_calibration_value;
}
-void AnalogAudioView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
+void AnalogAudioView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions inside AnalogAudioView to write iq_phase_calibration_value
iq_phase_calibration_value = cal_value;
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
}
@@ -246,6 +338,7 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
}
AnalogAudioView::~AnalogAudioView() {
+ receiver_model.set_hidden_offset(0);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
@@ -267,10 +360,6 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
}
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
- // This app doesn't know what to do with "Capture" mode.
- if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
- modulation = ReceiverModel::Mode::SpectrumAnalysis;
-
baseband::spectrum_streaming_stop();
update_modulation(modulation);
on_show_options_modulation();
@@ -329,7 +418,7 @@ void AnalogAudioView::on_show_options_modulation() {
const auto modulation = receiver_model.modulation();
switch (modulation) {
case ReceiverModel::Mode::AMAudio:
- widget = std::make_unique(options_view_rect, Theme::getInstance()->option_active);
+ widget = std::make_unique(this, options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
text_ctcss.hidden(true);
break;
@@ -346,6 +435,18 @@ void AnalogAudioView::on_show_options_modulation() {
text_ctcss.hidden(true);
break;
+ case ReceiverModel::Mode::WFMAudioAMApt:
+ widget = std::make_unique(options_view_rect, Theme::getInstance()->option_active);
+ waterfall.show_audio_spectrum_view(true);
+ text_ctcss.hidden(true);
+ break;
+
+ case ReceiverModel::Mode::AMAudioFMApt:
+ widget = std::make_unique(this, options_view_rect, Theme::getInstance()->option_active);
+ waterfall.show_audio_spectrum_view(false);
+ text_ctcss.hidden(true);
+ break;
+
case ReceiverModel::Mode::SpectrumAnalysis:
widget = std::make_unique(this, nbfm_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
@@ -387,6 +488,12 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
+ case ReceiverModel::Mode::WFMAudioAMApt:
+ image_tag = portapack::spi_flash::image_tag_wfm_audio;
+ break;
+ case ReceiverModel::Mode::AMAudioFMApt:
+ image_tag = portapack::spi_flash::image_tag_am_audio;
+ break;
case ReceiverModel::Mode::SpectrumAnalysis:
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
break;
@@ -407,6 +514,8 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
+ receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
+
receiver_model.enable();
// TODO: This doesn't belong here! There's a better way.
@@ -421,6 +530,12 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
sampling_rate = 48000;
break;
+ case ReceiverModel::Mode::WFMAudioAMApt:
+ sampling_rate = 12000;
+ break;
+ case ReceiverModel::Mode::AMAudioFMApt:
+ sampling_rate = 12000;
+ break;
default:
break;
}
diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp
index 33153376a..5979aecc0 100644
--- a/firmware/application/apps/analog_audio_app.hpp
+++ b/firmware/application/apps/analog_audio_app.hpp
@@ -35,9 +35,11 @@
namespace ui {
+class AnalogAudioView;
+
class AMOptionsView : public View {
public:
- AMOptionsView(Rect parent_rect, const Style* style);
+ AMOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -51,6 +53,38 @@ class AMOptionsView : public View {
{
// Using common messages from freqman_ui.cpp
}};
+
+ OptionsField zoom_config{
+ {23 * 8, 0 * 16},
+ 7,
+ {{"ZOOM x1", 0},
+ {"ZOOM x2", 6}} // offset index AM modes array FIR filters.
+ };
+};
+
+class AMFMAptOptionsView : public View {
+ public:
+ AMFMAptOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
+
+ private:
+ Text label_config{
+ {0 * 8, 0 * 16, 2 * 8, 1 * 16},
+ "BW",
+ };
+
+ OptionsField options_config{
+ {3 * 8, 0 * 16},
+ 17, // Max option length chars "USB+FM(Wefax Apt)"
+ {
+ // Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
+ }};
+
+ OptionsField zoom_config{
+ {23 * 8, 0 * 16},
+ 7,
+ {{"ZOOM x1", 0},
+ {"ZOOM x2", 6}} // offset index array filters.
+ };
};
class NBFMOptionsView : public View {
@@ -98,7 +132,22 @@ class WFMOptionsView : public View {
}};
};
-class AnalogAudioView;
+class WFMAMAptOptionsView : public View {
+ public:
+ WFMAMAptOptionsView(Rect parent_rect, const Style* style);
+
+ private:
+ Text label_config{
+ {0 * 8, 0 * 16, 2 * 8, 1 * 16},
+ "BW",
+ };
+ OptionsField options_config{
+ {3 * 8, 0 * 16},
+ 16, // Max option char length "80k-NOAA Apt LPF" , example.
+ {
+ // Using common messages from freqman_ui.cpp
+ }};
+};
class SPECOptionsView : public View {
public:
@@ -136,7 +185,7 @@ class SPECOptionsView : public View {
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
"Rx_IQ_CAL "};
NumberField field_rx_iq_phase_cal{
- {28 * 8, 0 * 16},
+ {screen_width - 2 * 8, 0 * 16},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
@@ -164,20 +213,38 @@ class AnalogAudioView : public View {
uint8_t get_spec_iq_phase_calibration_value();
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
+ uint8_t get_zoom_factor(uint8_t mode);
+ void set_zoom_factor(uint8_t mode, uint8_t zoom);
+
+ uint8_t get_previous_AM_mode_option();
+ void set_previous_AM_mode_option(uint8_t mode);
+
+ uint8_t get_previous_zoom_option();
+ void set_previous_zoom_option(uint8_t zoom);
+
private:
static constexpr ui::Dim header_height = 3 * 16;
NavigationView& nav_;
RxRadioState radio_state_{};
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
+ uint8_t zoom_factor_am{0}; // initial zoom factor in AM mode
+ uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
+ uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
+ uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
+
app_settings::SettingsManager settings_{
"rx_audio",
app_settings::Mode::RX,
{
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
+ {"zoom_factor_am"sv, &zoom_factor_am}, // we are saving and restoring AM ZOOM factor from Settings.
+ {"zoom_factor_amfm"sv, &zoom_factor_amfm}, // we are saving and restoring AMFM ZOOM factor from Settings.
+ {"previous_AM_mode_option"sv, &previous_AM_mode_option}, // we are saving and restoring AMFM ZOOM factor from Settings.
+ {"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
}};
- const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
+ const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
size_t spec_bw_index = 0;
@@ -211,10 +278,12 @@ class AnalogAudioView : public View {
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
+ {"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
+ {"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
Text text_ctcss{
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
@@ -223,7 +292,7 @@ class AnalogAudioView : public View {
std::unique_ptr options_widget{};
RecordView record_view{
- {0 * 8, 2 * 16, 30 * 8, 1 * 16},
+ {0 * 8, 2 * 16, screen_width, 1 * 16},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
diff --git a/firmware/application/apps/ble_comm_app.hpp b/firmware/application/apps/ble_comm_app.hpp
index 246d08c08..43b07b4be 100644
--- a/firmware/application/apps/ble_comm_app.hpp
+++ b/firmware/application/apps/ble_comm_app.hpp
@@ -170,7 +170,7 @@ class BLECommView : public View {
"-"};
Console console{
- {0, 4 * 16, 240, 240}};
+ {0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
std::string str_log{""};
bool logging{false};
diff --git a/firmware/application/apps/ble_rx_app.cpp b/firmware/application/apps/ble_rx_app.cpp
index 16af4f85f..136e0dd52 100644
--- a/firmware/application/apps/ble_rx_app.cpp
+++ b/firmware/application/apps/ble_rx_app.cpp
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
+ * Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -21,7 +22,6 @@
* Boston, MA 02110-1301, USA.
*/
-#include "ble_rx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_modemsetup.hpp"
@@ -34,11 +34,20 @@
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
+#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
+#define BLE_RX_NO_ERROR 0
+#define BLE_RX_LIST_FILENAME_EMPTY_ERROR 1
+#define BLE_RX_ENTRY_FILENAME_EMPTY_ERROR 2
+#define BLE_RX_LIST_SAVE_ERROR 3
+#define BLE_RX_ENTRY_SAVE_ERROR 4
+
+static uint8_t ble_rx_error = BLE_RX_NO_ERROR;
+
void BLELogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
@@ -59,6 +68,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
return result;
}
+MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
+ static bool db_checked = false;
+ static bool db_exists = false;
+
+ if (!db_checked) {
+ database db;
+ database::MacAddressDBRecord dummy_record;
+ int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
+ db_exists = (test_result != DATABASE_NOT_FOUND);
+ db_checked = true;
+ }
+
+ if (!db_exists) {
+ vendor_name = "macaddress.db not found";
+ return MAC_DB_NOT_FOUND;
+ }
+
+ database db;
+ database::MacAddressDBRecord record;
+
+ // Convert MAC address to hex string
+ std::string mac_hex = "";
+ for (int i = 0; i < 3; i++) {
+ // Only need first 3 bytes for OUI
+ mac_hex += to_string_hex(mac_address[i], 2);
+ }
+
+ int result = db.retrieve_macaddress_record(&record, mac_hex);
+
+ if (result == DATABASE_RECORD_FOUND) {
+ vendor_name = std::string(record.vendor_name);
+ return MAC_VENDOR_FOUND;
+ } else {
+ vendor_name = "Unknown";
+ return MAC_VENDOR_NOT_FOUND;
+ }
+}
+
+std::string lookup_mac_vendor(const uint8_t* mac_address) {
+ std::string vendor_name;
+ lookup_mac_vendor_status(mac_address, vendor_name);
+ return vendor_name;
+}
+
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
@@ -142,8 +195,15 @@ void RecentEntriesTable::draw(
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
+ std::string hitsStr;
+
+ if (!entry.informationString.empty()) {
+ hitsStr = entry.informationString;
+ } else {
+ hitsStr = to_string_dec_int(entry.numHits);
+ }
+
// Pushing single digit values down right justified.
- std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
@@ -157,7 +217,10 @@ void RecentEntriesTable::draw(
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
- painter.draw_string(target_rect.location(), style, line);
+
+ Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
+
+ painter.draw_string(target_rect.location(), row_style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
@@ -170,10 +233,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
+ &label_vendor,
+ &text_vendor,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
+ std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
+ text_vendor.set(vendor_name);
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
@@ -195,6 +262,7 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
nav,
packetFileBuffer,
64,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this, packetToSave](std::string& buffer) {
on_save_file(buffer, packetToSave);
});
@@ -202,19 +270,23 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
}
void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
- ensure_directory(packet_save_path);
- auto folder = packet_save_path.parent_path();
- auto ext = packet_save_path.extension();
- auto new_path = folder / value + ext;
-
- saveFile(new_path, packetToSave);
+ if (value.length() > 0) {
+ ensure_directory(packet_save_path);
+ auto folder = packet_save_path.parent_path();
+ auto ext = packet_save_path.extension();
+ auto new_path = folder / value + ext;
+ ble_rx_error = saveFile(new_path, packetToSave);
+ } else {
+ nav_.pop();
+ ble_rx_error = BLE_RX_ENTRY_FILENAME_EMPTY_ERROR;
+ }
}
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
File f;
auto error = f.create(path);
if (error)
- return false;
+ return BLE_RX_ENTRY_SAVE_ERROR;
std::string macAddressStr = packetToSave.macAddress;
std::string advertisementDataStr = packetToSave.advertisementData;
@@ -224,7 +296,7 @@ bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETx
f.write(packetString.c_str(), packetString.length());
- return true;
+ return BLE_RX_NO_ERROR;
}
void BleRecentEntryDetailView::update_data() {
@@ -357,6 +429,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
+ text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
+ text_pdu_type.set(pdu_type_to_string(entry.pduType));
+ std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
+ text_vendor.set(vendor_name);
set_dirty();
}
@@ -434,6 +510,8 @@ BLERxView::BLERxView(NavigationView& nav)
&button_switch,
&recent_entries_view});
+ async_tx_states_when_entered = portapack::async_tx_enabled;
+
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push(entry);
};
@@ -441,9 +519,9 @@ BLERxView::BLERxView(NavigationView& nav)
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
- usb_serial_thread = std::make_unique();
+ portapack::async_tx_enabled = true;
} else {
- usb_serial_thread.reset();
+ portapack::async_tx_enabled = false;
}
};
check_serial_log.set_value(serial_logging);
@@ -459,6 +537,7 @@ BLERxView::BLERxView(NavigationView& nav)
nav_,
filterBuffer,
64,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_filter_change(buffer);
});
@@ -467,7 +546,6 @@ BLERxView::BLERxView(NavigationView& nav)
logger = std::make_unique();
check_log.on_select = [this](Checkbox&, bool v) {
- str_log = "";
logging = v;
if (logger && logging)
@@ -481,6 +559,7 @@ BLERxView::BLERxView(NavigationView& nav)
nav,
listFileBuffer,
64,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_save_file(buffer);
});
@@ -488,6 +567,7 @@ BLERxView::BLERxView(NavigationView& nav)
button_clear_list.on_select = [this](Button&) {
recent.clear();
+ recent_entries_view.set_dirty();
};
button_switch.on_select = [&nav](Button&) {
@@ -529,7 +609,10 @@ BLERxView::BLERxView(NavigationView& nav)
options_filter.on_change = [this](size_t index, int32_t v) {
filter_index = (uint8_t)index;
+ recent.clear();
handle_filter_options(v);
+ uniqueParsing = filter_index == 2 ? true : false;
+ recent_entries_view.set_dirty();
};
options_channel.set_selected_index(channel_index, true);
@@ -568,11 +651,14 @@ std::string BLERxView::build_line_str(BleRecentEntry entry) {
}
void BLERxView::on_save_file(const std::string value) {
- auto folder = packet_save_path.parent_path();
- auto ext = packet_save_path.extension();
- auto new_path = folder / value + ext;
-
- saveFile(new_path);
+ if (value.length() > 0) {
+ auto folder = packet_save_path.parent_path();
+ auto ext = packet_save_path.extension();
+ auto new_path = folder / value + ext;
+ ble_rx_error = saveFile(new_path);
+ } else {
+ ble_rx_error = BLE_RX_LIST_FILENAME_EMPTY_ERROR;
+ }
}
bool BLERxView::saveFile(const std::filesystem::path& path) {
@@ -584,7 +670,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
auto error = src->open(path, false, true);
if (error) {
- return false;
+ return BLE_RX_LIST_SAVE_ERROR;
}
for (const auto& entry : recent) {
@@ -615,7 +701,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
auto error = dst->open(tempFilePath, false, true);
if (error) {
- return false;
+ return BLE_RX_LIST_SAVE_ERROR;
}
dst->write_line(headerStr.c_str());
@@ -695,14 +781,65 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
tempList.clear();
- return true;
+ return BLE_RX_NO_ERROR;
}
void BLERxView::on_data(BlePacketData* packet) {
- if (!logging) {
- str_log = "";
+ uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
+
+ // Start of Packet stuffing.
+ // Masking off the top 2 bytes to avoid invalid keys.
+
+ uint64_t key = macAddressEncoded & 0xFFFFFFFFFFFF;
+ bool packetExists = false;
+
+ // If found store into tempEntry to modify.
+ auto it = find(recent, key);
+ if (it != recent.end()) {
+ recent.push_front(*it);
+ recent.erase(it);
+ updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
+ packetExists = true;
+ } else {
+ recent.emplace_front(key);
+ truncate_entries(recent);
+
+ packetExists = updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
+
+ // If parsing failed, remove entry.
+ if (!packetExists) {
+ recent.erase(recent.begin());
+ }
}
+ if (packetExists) {
+ handle_filter_options(options_filter.selected_index());
+ handle_entries_sort(options_sort.selected_index());
+
+ if (!searchList.empty()) {
+ auto it = searchList.begin();
+
+ while (it != searchList.end()) {
+ std::string searchStr = (std::string)*it;
+
+ if (recent.front().dataString.find(searchStr) != std::string::npos) {
+ searchList.erase(it);
+ found_count++;
+ break;
+ }
+
+ it++;
+ }
+
+ text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
+ }
+ }
+
+ log_ble_packet(packet);
+}
+
+void BLERxView::log_ble_packet(BlePacketData* packet) {
+ str_console = "";
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
@@ -716,49 +853,13 @@ void BLERxView::on_data(BlePacketData* packet) {
str_console += to_string_hex(packet->data[i], 2);
}
- uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
-
- // Start of Packet stuffing.
- // Masking off the top 2 bytes to avoid invalid keys.
- auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
- updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
-
- // Add entries if they meet the criteria.
- // auto value = filter;
- // resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
- // return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
- // });
- handle_filter_options(options_filter.selected_index());
-
- handle_entries_sort(options_sort.selected_index());
-
// Log at End of Packet.
if (logger && logging) {
- logger->log_raw_data(str_console + "\r\n");
+ logger->log_raw_data(str_console);
}
if (serial_logging) {
- usb_serial_thread->serial_str = str_console + "\r\n";
- usb_serial_thread->str_ready = true;
- }
- str_console = "";
-
- if (!searchList.empty()) {
- auto it = searchList.begin();
-
- while (it != searchList.end()) {
- std::string searchStr = (std::string)*it;
-
- if (entry.dataString.find(searchStr) != std::string::npos) {
- searchList.erase(it);
- found_count++;
- break;
- }
-
- it++;
- }
-
- text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
+ UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
}
}
@@ -822,15 +923,24 @@ void BLERxView::on_timer() {
timer_count = 0;
if (auto_channel) {
- int min = 37;
- int max = 39;
+ field_frequency.set_value(get_freq_by_channel_number(channel_number));
+ baseband::set_btlerx(channel_number);
- int randomChannel = min + std::rand() % (max - min + 1);
-
- field_frequency.set_value(get_freq_by_channel_number(randomChannel));
- baseband::set_btlerx(randomChannel);
+ channel_number = (channel_number < 39) ? channel_number + 1 : 37;
}
}
+ if (ble_rx_error != BLE_RX_NO_ERROR) {
+ if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
+ nav_.display_modal("Error", "List filename is empty !");
+ } else if (ble_rx_error == BLE_RX_ENTRY_FILENAME_EMPTY_ERROR) {
+ nav_.display_modal("Error", "Entry filename is empty !");
+ } else if (ble_rx_error == BLE_RX_LIST_SAVE_ERROR) {
+ nav_.display_modal("Error", "Couldn't save list !");
+ } else if (ble_rx_error == BLE_RX_ENTRY_SAVE_ERROR) {
+ nav_.display_modal("Error", "Couldn't save entry !");
+ }
+ ble_rx_error = BLE_RX_NO_ERROR;
+ }
}
void BLERxView::handle_entries_sort(uint8_t index) {
@@ -887,20 +997,23 @@ void BLERxView::set_parent_rect(const Rect new_parent_rect) {
}
BLERxView::~BLERxView() {
+ portapack::async_tx_enabled = async_tx_states_when_entered;
receiver_model.disable();
baseband::shutdown();
}
-void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
+bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
+ bool success = false;
+
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
- entry.dbValue = packet->max_dB;
+ entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
@@ -916,9 +1029,14 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
- entry.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
+ entry.numHits++;
+
+ if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
+ std::string vendor_name;
+ entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
+ }
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
@@ -928,35 +1046,55 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
entry.include_name = check_name.value();
// Only parse name for advertisment packets and empty name entries
- if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
- ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
-
- uint8_t currentByte = 0;
- uint8_t length = 0;
- uint8_t type = 0;
-
- std::string decoded_data;
- for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
- length = advertiseData->Data[currentByte++];
- type = advertiseData->Data[currentByte++];
-
- // Subtract 1 because type is part of the length.
- for (int i = 0; i < length - 1; i++) {
- // parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
- if (type == 0x08 || type == 0x09) {
- decoded_data += (char)advertiseData->Data[currentByte];
- }
- currentByte++;
- }
- if (!decoded_data.empty()) {
- entry.nameString = std::move(decoded_data);
- break;
- }
+ if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
+ if (uniqueParsing) {
+ // Add your unique beacon parsing function here.
}
+
+ if (!success && !uniqueParsing) {
+ success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
+ }
+
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
}
+
+ return success;
+}
+
+bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
+ uint8_t currentByte, currentLength, currentType = 0;
+ std::string tempName = "";
+
+ for (currentByte = 0; currentByte < length;) {
+ currentLength = data[currentByte++];
+ currentType = data[currentByte++];
+
+ // Subtract 1 because type is part of the length.
+ for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
+ // parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
+ if (currentType == 0x08 || currentType == 0x09) {
+ tempName += (char)data[currentByte];
+ }
+ currentByte++;
+ }
+
+ if (!tempName.empty()) {
+ nameString = tempName;
+ break;
+ }
+ }
+
+ informationString = "";
+
+ if (!informationString.empty()) {
+ // Option to change title of Hits Column.
+ // Setting to default for now.
+ columns.set(1, "Hits", 7);
+ }
+
+ return true;
}
} /* namespace ui */
diff --git a/firmware/application/apps/ble_rx_app.hpp b/firmware/application/apps/ble_rx_app.hpp
index 6befa2828..a1654e950 100644
--- a/firmware/application/apps/ble_rx_app.hpp
+++ b/firmware/application/apps/ble_rx_app.hpp
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
+ * Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -33,6 +34,7 @@
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
+#include "database.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
@@ -72,10 +74,17 @@ typedef enum {
RESERVED8 = 15
} ADV_PDU_TYPE;
+typedef enum {
+ MAC_VENDOR_UNKNOWN = 0,
+ MAC_VENDOR_FOUND = 1,
+ MAC_VENDOR_NOT_FOUND = 2,
+ MAC_DB_NOT_FOUND = 3
+} MAC_VENDOR_STATUS;
+
struct BleRecentEntry {
using Key = uint64_t;
- static constexpr Key invalid_key = 0xffffffff;
+ static constexpr Key invalid_key = 0xFFFFFFFFFFFF;
uint64_t macAddress;
int dbValue;
@@ -83,10 +92,12 @@ struct BleRecentEntry {
std::string timestamp;
std::string dataString;
std::string nameString;
+ std::string informationString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
+ MAC_VENDOR_STATUS vendor_status;
bool entryFound;
BleRecentEntry()
@@ -101,10 +112,12 @@ struct BleRecentEntry {
timestamp{},
dataString{},
nameString{},
+ informationString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
+ vendor_status{MAC_VENDOR_UNKNOWN},
entryFound{} {
}
@@ -152,6 +165,13 @@ class BleRecentEntryDetailView : public View {
{9 * 8, 1 * 16, 17 * 8, 16},
"-"};
+ Labels label_vendor{
+ {{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
+
+ Text text_vendor{
+ {7 * 8, 2 * 16, 23 * 8, 16},
+ "-"};
+
Labels labels{
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
@@ -198,15 +218,18 @@ class BLERxView : public View {
bool saveFile(const std::filesystem::path& path);
std::unique_ptr usb_serial_thread{};
void on_data(BlePacketData* packetData);
+ void log_ble_packet(BlePacketData* packet);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void handle_filter_options(uint8_t index);
- void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
+ bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
+ bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
NavigationView& nav_;
+
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
@@ -216,9 +239,11 @@ class BLERxView : public View {
uint8_t channel_index{0};
uint8_t sort_index{0};
uint8_t filter_index{0};
+ bool uniqueParsing = false;
std::string filter{};
bool logging{false};
bool serial_logging{false};
+ bool async_tx_states_when_entered{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
@@ -242,7 +267,7 @@ class BLERxView : public View {
bool auto_channel = false;
int16_t timer_count{0};
- int16_t timer_period{6}; // 100ms
+ int16_t timer_period{2}; // 25ms
std::string filterBuffer{};
std::string listFileBuffer{};
@@ -306,9 +331,10 @@ class BLERxView : public View {
OptionsField options_filter{
{18 * 8 + 2, 2 * 8},
- 4,
+ 7,
{{"Data", 0},
- {"MAC", 1}}};
+ {"MAC", 1},
+ {"Unique", 2}}};
Checkbox check_log{
{10 * 8, 4 * 8 + 2},
@@ -340,18 +366,18 @@ class BLERxView : public View {
true};
// Console console{
- // {0, 10 * 8, 240, 240}};
+ // {0, 10 * 8, screen_height, screen_height-80}};
Button button_clear_list{
- {2 * 8, 320 - (16 + 32), 7 * 8, 32},
+ {2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
- {11 * 8, 320 - (16 + 32), 11 * 8, 32},
+ {11 * 8, screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
- {240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
+ {screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
@@ -360,10 +386,10 @@ class BLERxView : public View {
BleRecentEntries recent{};
BleRecentEntries tempList{};
- const RecentEntriesColumns columns{{
- {"Mac Address", 17},
+ RecentEntriesColumns columns{{
+ {"Name", 17},
{"Hits", 7},
- {"dB", 4},
+ {"dBm", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
diff --git a/firmware/application/apps/ble_tx_app.cpp b/firmware/application/apps/ble_tx_app.cpp
index d9680c4d4..b0ade7b1a 100644
--- a/firmware/application/apps/ble_tx_app.cpp
+++ b/firmware/application/apps/ble_tx_app.cpp
@@ -319,7 +319,8 @@ void BLETxView::on_tx_progress(const bool done) {
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
- add_children({&button_open,
+ add_children({&dataEditView,
+ &button_open,
&text_filename,
&progressbar,
&check_rand_mac,
@@ -340,7 +341,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_mac_address,
&text_mac_address,
&label_data_packet,
- &dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
@@ -399,6 +399,7 @@ BLETxView::BLETxView(NavigationView& nav)
nav,
packetFileBuffer,
64,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_save_file(buffer);
});
@@ -429,6 +430,23 @@ BLETxView::BLETxView(NavigationView& nav)
}
};
+ dataEditView.on_change = [this](uint8_t value) {
+ // Reject setting newline at index 29.
+ if (cursor_pos.col != 29) {
+ uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
+
+ packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
+
+ update_current_packet(packets[current_packet], current_packet);
+ }
+ };
+
+ dataEditView.on_cursor_moved = [this]() {
+ // Save last selected cursor.
+ cursor_pos.line = dataEditView.line();
+ cursor_pos.col = dataEditView.col();
+ };
+
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
@@ -530,7 +548,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
- ;
}
dataFile.~File();
@@ -545,6 +562,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
+ dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
}
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
diff --git a/firmware/application/apps/ble_tx_app.hpp b/firmware/application/apps/ble_tx_app.hpp
index 534201b71..0d8c34bc0 100644
--- a/firmware/application/apps/ble_tx_app.hpp
+++ b/firmware/application/apps/ble_tx_app.hpp
@@ -216,7 +216,7 @@ class BLETxView : public View {
true};
ImageButton button_play{
- {28 * 8, 2 * 16, 2 * 8, 1 * 16},
+ {screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
@@ -287,11 +287,8 @@ class BLETxView : public View {
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
- Console console{
- {0, 9 * 18, 240, 240}};
-
TextViewer dataEditView{
- {0, 9 * 18, 240, 240}};
+ {0, 9 * 18, screen_width, screen_height - 80}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
diff --git a/firmware/application/apps/capture_app.cpp b/firmware/application/apps/capture_app.cpp
index ca5c5477b..53249e094 100644
--- a/firmware/application/apps/capture_app.cpp
+++ b/firmware/application/apps/capture_app.cpp
@@ -55,22 +55,24 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
this->field_frequency.set_step(v);
};
- option_format.set_selected_index(0); // Default to C16
+ option_format.set_selected_index(file_format);
option_format.on_change = [this](size_t, uint32_t file_type) {
+ file_format = file_type;
record_view.set_file_type((RecordView::FileType)file_type);
};
+ check_trim.set_value(trim);
check_trim.on_select = [this](Checkbox&, bool v) {
+ trim = v;
record_view.set_auto_trim(v);
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
- option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
+ option_bandwidth.on_change = [this](size_t, uint32_t new_capture_rate) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
- auto sample_rate = bandwidth;
- /* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
+ /* capture_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
@@ -78,7 +80,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
// record_view determines the correct oversampling to apply and returns the actual sample rate.
// NB: record_view is what actually updates proc_capture baseband settings.
- auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
+ auto actual_sample_rate = record_view.set_sampling_rate(new_capture_rate);
// Update the radio model with the actual sampling rate.
receiver_model.set_sampling_rate(actual_sample_rate);
@@ -88,19 +90,19 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
- if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
+ if ((new_capture_rate >= 1500000) && (capture_rate < 1500000)) {
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
}
- if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
+ if ((new_capture_rate <= 1250000) && (capture_rate > 1250000)) {
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
}
- previous_bandwidth = bandwidth;
+ capture_rate = new_capture_rate;
waterfall.start();
};
receiver_model.enable();
- option_bandwidth.set_by_value(500000);
+ option_bandwidth.set_by_value(capture_rate);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp
index d86f9c14f..12ae2854e 100644
--- a/firmware/application/apps/capture_app.hpp
+++ b/firmware/application/apps/capture_app.hpp
@@ -48,12 +48,21 @@ class CaptureAppView : public View {
private:
static constexpr ui::Dim header_height = 3 * 16;
- uint32_t previous_bandwidth{500000};
+
+ uint32_t capture_rate{500000};
+ uint32_t file_format{0};
+ bool trim{false};
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
- "rx_capture", app_settings::Mode::RX};
+ "rx_capture",
+ app_settings::Mode::RX,
+ {
+ {"capture_rate"sv, &capture_rate},
+ {"file_format"sv, &file_format},
+ {"trim"sv, &trim},
+ }};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
@@ -100,7 +109,7 @@ class CaptureAppView : public View {
/*small*/ true};
RecordView record_view{
- {0 * 8, 2 * 16, 30 * 8, 1 * 16},
+ {0 * 8, 2 * 16, screen_width, 1 * 16},
u"BBD_????.*",
captures_dir,
RecordView::FileType::RawS16,
diff --git a/firmware/application/apps/ert_app.hpp b/firmware/application/apps/ert_app.hpp
index 9172ca8ed..9e12fe917 100644
--- a/firmware/application/apps/ert_app.hpp
+++ b/firmware/application/apps/ert_app.hpp
@@ -166,7 +166,7 @@ class ERTAppView : public View {
};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_packet{
Message::ID::ERTPacket,
diff --git a/firmware/application/apps/pocsag_app.hpp b/firmware/application/apps/pocsag_app.hpp
index fb7a5cd37..45a01f3aa 100644
--- a/firmware/application/apps/pocsag_app.hpp
+++ b/firmware/application/apps/pocsag_app.hpp
@@ -259,7 +259,7 @@ class POCSAGAppView : public View {
' ',
true /*wrap*/};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
diff --git a/firmware/application/apps/soundboard_app.cpp b/firmware/application/apps/soundboard_app.cpp
index b8af1d0f1..e8159a4a0 100644
--- a/firmware/application/apps/soundboard_app.cpp
+++ b/firmware/application/apps/soundboard_app.cpp
@@ -91,10 +91,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
auto reader = std::make_unique();
- uint32_t tone_key_index = options_tone_key.selected_index();
- uint32_t sample_rate;
- uint8_t bits_per_sample;
-
stop();
if (!reader->open(u"/WAV/" + file_list[id].native())) {
@@ -108,7 +104,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
// button_play.set_bitmap(&bitmap_stop);
- sample_rate = reader->sample_rate();
+ uint32_t sample_rate = reader->sample_rate();
+
+ tone_key_index = options_tone_key.selected_index();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique(
@@ -155,6 +153,23 @@ void SoundBoardView::on_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
+void SoundBoardView::update_config() {
+ // NB: this were called by the on_bandwidth_changed() callback,
+ // so other val would be updated too when bw changed. currently it's safe but be careful.
+ baseband::set_audiotx_config(
+ 1536000 / 20, // Update vu-meter at 20Hz
+ transmitter_model.channel_bandwidth(),
+ 0, // Gain is unused
+ 8, // shift_bits_s16, default 8 bits, but also unused
+ bits_per_sample,
+ TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
+ false, // AM
+ false, // DSB
+ false, // USB
+ false // LSB
+ );
+}
+
void SoundBoardView::on_select_entry() {
tx_view.focus();
}
@@ -175,7 +190,8 @@ void SoundBoardView::refresh_list() {
for (auto& c : entry_extension)
c = toupper(c);
- if (entry_extension == ".WAV") {
+ if (entry_extension == ".WAV" && entry.path().string().find("shopping_cart") == std::string::npos) {
+ /* ^ because the shopping cart lock app using the speaker to send the LF signal, it's meaningless to be here */
if (reader->open(wav_dir / entry.path())) {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();
@@ -288,6 +304,10 @@ SoundBoardView::SoundBoardView(
};
};
+ tx_view.on_bandwidth_changed = [this]() {
+ update_config();
+ };
+
tx_view.on_start = [this]() {
start_tx(menu_view.highlighted_index());
};
diff --git a/firmware/application/apps/soundboard_app.hpp b/firmware/application/apps/soundboard_app.hpp
index 04aeb5405..72b9fd063 100644
--- a/firmware/application/apps/soundboard_app.hpp
+++ b/firmware/application/apps/soundboard_app.hpp
@@ -70,6 +70,8 @@ class SoundBoardView : public View {
uint32_t playing_id{};
uint32_t page = 1;
uint32_t c_page = 1;
+ uint32_t tone_key_index = 1;
+ uint8_t bits_per_sample = 1;
std::vector file_list{};
@@ -90,6 +92,7 @@ class SoundBoardView : public View {
void on_tx_progress(const uint32_t progress);
void refresh_list();
void on_select_entry();
+ void update_config();
Labels labels{
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
@@ -104,10 +107,10 @@ class SoundBoardView : public View {
"<="};
Text page_info{
- {0, 29 * 8, 30 * 8, 16}};
+ {0, 29 * 8, screen_width, 16}};
MenuView menu_view{
- {0, 0, 240, 175},
+ {0, 0, screen_width, 175},
true};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
@@ -128,9 +131,9 @@ class SoundBoardView : public View {
{}};
AudioVolumeField field_volume{
- {28 * 8, 180}};
+ {screen_width - 2 * 8, 180}};
Text text_volume_disabled{
- {28 * 8, 180, 3 * 8, 16},
+ {screen_width - 2 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
@@ -144,7 +147,7 @@ class SoundBoardView : public View {
"Random"};
ProgressBar progressbar{
- {0 * 8, 31 * 8 + 2, 30 * 8, 4}};
+ {0 * 8, 31 * 8 + 2, screen_width, 4}};
TransmitterView tx_view{
16 * 16,
diff --git a/firmware/application/apps/ui_about_simple.cpp b/firmware/application/apps/ui_about_simple.cpp
index 1e78e5e93..953999bbc 100644
--- a/firmware/application/apps/ui_about_simple.cpp
+++ b/firmware/application/apps/ui_about_simple.cpp
@@ -7,30 +7,34 @@
namespace ui {
// Information: a line starting with a '#' will be yellow coloured
-constexpr std::string_view authors_list[] = {
- "# * List of contributors * ",
+constexpr std::string_view mayhem_information_list[] = {
+ "#****** Mayhem Community ******",
+ " ",
+ " https://discord.mayhem.app",
+ " ",
+ "#**** List of contributors ****",
" ",
"#Mayhem-Firmware:",
"jboone,eried,furrtek,",
"NotherNgineer,gullradriel,",
"jLynx,kallanreed,Brumi-2021,",
- "htotoo,bernd-herzog,zxkmm,",
+ "htotoo,zxkmm,bernd-herzog,",
"ArjanOnwezen,euquiq,u-foka,",
"iNetro,heurist1,dqs105,",
"teixeluis,jwetzell,",
"jimilinuxguy,gregoryfenton,",
"notpike,strijar,BehleZebub,",
- "arneluehrs,rascafr,joyel24,",
- "ImDroided,zigad,johnelder,",
- "klockee,nnesetto,LupusE,",
- "argilo,dc2dc,formtapez,",
+ "arneluehrs,mcules,rascafr,",
+ "joyel24,ImDroided,zigad,",
+ "johnelder,klockee,nnesetto,",
+ "LupusE,argilo,dc2dc,formtapez,",
"RocketGod-git,mrmookie,",
"ITAxReal,F33RNI,F4GEV,",
"rusty-labs,mjwaxios,andrej-mk,",
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
- "MatiasFernandez,Giorgiofox,",
- "ckuethe",
+ "MatiasFernandez,Giorgiofox",
+ "TommasoVentafridda",
" ",
"#Havoc:",
"jboone,furrtek,eried,argilo,",
@@ -72,7 +76,7 @@ AboutView::AboutView(NavigationView& nav) {
button_ok.focus();
};
- for (auto& authors_line : authors_list) {
+ for (auto& authors_line : mayhem_information_list) {
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
if (authors_line.size() > 0) {
if (authors_line[0] == '#') {
@@ -103,15 +107,13 @@ void AboutView::on_frame_sync() {
menu_view.set_highlighted(current + 1);
} else {
menu_view.set_highlighted(0);
- // ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
- menu_view.set_highlighted(2); // the first line that has human name
}
}
}
void AboutView::focus() {
button_ok.focus();
- menu_view.set_highlighted(2); // the first line that has human name
+ menu_view.set_highlighted(3); // contributors block starting line
}
bool AboutView::on_touch(const TouchEvent) {
diff --git a/firmware/application/apps/ui_about_simple.hpp b/firmware/application/apps/ui_about_simple.hpp
index 637a03127..3c0a349d0 100644
--- a/firmware/application/apps/ui_about_simple.hpp
+++ b/firmware/application/apps/ui_about_simple.hpp
@@ -6,6 +6,10 @@
#include
+/*
+ TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
+*/
+
namespace ui {
class AboutView : public View {
public:
@@ -22,11 +26,11 @@ class AboutView : public View {
uint16_t frame_sync_count{0};
void on_frame_sync();
MenuView menu_view{
- {0, 0, 240, 264},
+ {0, 0, screen_width, screen_height - 56},
true};
Button button_ok{
- {240 / 3, 270, 240 / 3, 24},
+ {screen_width / 3, screen_height - 50, screen_width / 3, 24},
"OK",
};
diff --git a/firmware/application/apps/ui_adsb_rx.cpp b/firmware/application/apps/ui_adsb_rx.cpp
index 8ec63ec02..83a60eedc 100644
--- a/firmware/application/apps/ui_adsb_rx.cpp
+++ b/firmware/application/apps/ui_adsb_rx.cpp
@@ -40,6 +40,8 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
+static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
+
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
@@ -70,18 +72,34 @@ void RecentEntriesTable::draw(
entry_string +=
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
- to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
- to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
- to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
- (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
- to_string_dec_uint(entry.age, 4);
+ to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
+
+ if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
+ // It is generally accepted that for every thousand feet of altitude,
+ // true airspeed is approximately 2% higher than indicated airspeed.
+ // Since the application CPU has no floating point unit, we avoid floating point here
+ // tas = entry.velo.speed + (float)entry.pos.altitude / 1000.0 * 0.02 * entry.velo.speed;
+ unsigned int tas = entry.velo.speed + entry.pos.altitude * 2 * entry.velo.speed / 100000;
+
+ entry_string +=
+ to_string_dec_uint(tas, 4) + '*' +
+ to_string_dec_uint((unsigned int)(entry.amp >> 9), 3);
+ } else {
+ entry_string +=
+ to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
+ to_string_dec_uint((unsigned int)(entry.amp >> 9), 4);
+ }
+
+ entry_string += " " +
+ (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
+ to_string_dec_uint(entry.age, 4);
painter.draw_string(
target_rect.location(),
style,
entry_string);
- if (entry.pos.valid)
+ if (entry.pos.pos_valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
bitmap_target, target_color, style.background);
}
@@ -93,22 +111,31 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
log_line.reserve(100);
log_line = log_entry.raw_data;
- log_line += "ICAO:" + log_entry.icao;
+ log_line += " ICAO:" + log_entry.icao;
+
+ if (log_entry.sqwk)
+ log_line += " Squawk:" + to_string_dec_uint(log_entry.sqwk, 4, '0');
if (!log_entry.callsign.empty())
log_line += " " + log_entry.callsign;
- if (log_entry.pos.valid)
- log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
- " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
+ if (log_entry.pos.alt_valid)
+ log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude);
+
+ if (log_entry.pos.pos_valid)
+ log_line += " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
if (log_entry.vel.valid)
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
- " Spd: " + to_string_dec_int(log_entry.vel.speed);
+ speed_type_msg[log_entry.vel.type] +
+ to_string_dec_int(log_entry.vel.speed) +
+ " Vrate:" + to_string_dec_int(log_entry.vel.v_rate);
+
if (log_entry.sil != 0)
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
+
log_file.write_entry(log_line);
}
@@ -258,7 +285,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
- GeoPos::spd_unit::MPH,
+ GeoPos::spd_unit::HIDDEN,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
@@ -356,11 +383,12 @@ void ADSBRxDetailsView::refresh_ui() {
text_callsign.set(entry_.callsign);
text_infos.set(entry_.info_string);
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
+ std::string str_sqw = (entry_.sqwk > 0) ? " Sqw:" + to_string_dec_uint(entry_.sqwk) : "";
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
- " Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
+ speed_type_msg[entry_.velo.type] + to_string_dec_int(entry_.velo.speed) + str_sil + str_sqw);
else
- text_info2.set(str_sil);
+ text_info2.set(str_sil + str_sqw);
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
@@ -381,7 +409,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&status_good_frame,
&field_volume});
- recent_entries_view.set_parent_rect({0, 16, 240, 272});
+ recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push(entry);
@@ -421,14 +449,22 @@ void ADSBRxView::focus() {
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
auto frame = message->frame;
- uint32_t ICAO_address = frame.get_ICAO_address();
status_frame.toggle();
- // Bad frame, skip it.
- if (!frame.check_CRC() || ICAO_address == 0)
- return;
+ uint32_t ICAO_address;
+ uint32_t crc = frame.check_CRC();
+
+ if (crc != 0) {
+ if (find(recent, crc) != recent.end())
+ ICAO_address = crc;
+ else
+ return; // Bad frame, skip it.
+ } else {
+ ICAO_address = frame.get_ICAO_address();
+ if (ICAO_address == 0)
+ return; // Bad frame, skip it.
+ }
- ADSBLogEntry log_entry;
status_good_frame.toggle();
rtc::RTC datetime;
@@ -440,42 +476,68 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
entry.inc_hit();
entry.reset_age();
+ if (pmem::beep_on_packets()) {
+ baseband::request_audio_beep(1000, 24000, 60);
+ }
+
// Store smoothed amplitude on updates.
entry.amp = entry.hits == 0
? message->amp
: ((entry.amp * 15) + message->amp) >> 4;
- log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
+ uint8_t df = frame.get_DF();
+
+ if (df == 11) // do not log DF11, because messages arrive too frequently
+ return;
+
+ ADSBLogEntry log_entry;
+ uint8_t* raw_data = frame.get_raw_data();
+
+ if (df & 0x10) // 112 bits
+ log_entry.raw_data = to_string_hex_array(raw_data, 14);
+ else { // 56 bits
+ log_entry.raw_data = to_string_hex_array(raw_data, 7);
+ log_entry.raw_data.append(14, ' ');
+ }
+
log_entry.icao = entry.icao_str;
- if (frame.get_DF() == DF_ADSB) {
+ // 17: // Extended squitter
+ // 18: // Extended squitter/non-transponder
+ if (df == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
- uint8_t* raw_data = frame.get_raw_data();
- // 4: // surveillance, altitude reply
- if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
+ // transmitted when horizontal position information is not available but altitude information is available
+ if (msg_type == 0) {
+ // Q-bit must be present
+ if (raw_data[5] & 1) {
+ int altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
+
+ log_entry.pos.altitude = entry.pos.altitude = altitude;
+ log_entry.pos.alt_valid = entry.pos.alt_valid = true;
+ }
+ }
+ // 1-4: Aircraft identification
+ else if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
entry.set_callsign(decode_frame_id(frame));
log_entry.callsign = entry.callsign;
}
-
- // 9:
- // 18: // Extended squitter/non-transponder
- // 21: // Comm-B, identity reply
- // 20: // Comm-B, altitude reply
+ // 9-18: Airborne position (w/Baro Altitude)
+ // 20-22: Airborne position (w/GNSS Height)
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
log_entry.pos = entry.pos;
- if (entry.pos.valid) {
+ if (entry.pos.pos_valid) {
std::string str_info =
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
-
+ // 19: Airborne velocities
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
@@ -485,11 +547,59 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
}
}
- logger->log(log_entry);
+ // 4: // surveillance, altitude reply
+ // 20: // Comm-B, altitude reply
+ // 21: // Comm-B, identity reply
+ if (df == 0 || df == 4 || df == 20) { // Decode the 13 bit AC altitude field
+ uint8_t m_bit = raw_data[3] & (1 << 6);
+ uint8_t q_bit = raw_data[3] & (1 << 4);
+ int altitude = 0;
- if (pmem::beep_on_packets()) {
- baseband::request_audio_beep(1000, 24000, 60);
+ if (!m_bit) { // units -> FEET
+ if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M
+ int n = ((raw_data[2] & 31) << 6) |
+ ((raw_data[3] & 0x80) >> 2) |
+ ((raw_data[3] & 0x20) >> 1) |
+ (raw_data[3] & 15);
+
+ // The final altitude is due to the resulting number multiplied by 25, minus 1000.
+ altitude = 25 * n - 1000;
+ if (altitude < 0)
+ altitude = 0;
+ } // else N is an 11 bit Gillham coded altitude
+ }
+
+ log_entry.pos.altitude = entry.pos.altitude = altitude;
+ log_entry.pos.alt_valid = entry.pos.alt_valid = true;
}
+
+ if (df == 5 || df == 21 ||
+ (df == 17 && frame.get_msg_type() == 28 && frame.get_msg_sub() == 1)) { // Decode the squawk code
+ uint8_t* s = (df == 17) ? raw_data + 5 : raw_data + 2; // calc start of the code
+ uint16_t sqwk{0};
+
+ sqwk = ((s[1] & 0x80) >> 5) | ((s[0] & 0x02) >> 0) | ((s[0] & 0x08) >> 3); // A
+ sqwk *= 10;
+ sqwk += ((s[1] & 0x02) << 1) | ((s[1] & 0x08) >> 2) | ((s[1] & 0x20) >> 5); // B
+ sqwk *= 10;
+ sqwk += ((s[0] & 0x01) << 2) | ((s[0] & 0x04) >> 1) | ((s[0] & 0x10) >> 4); // C
+ sqwk *= 10;
+ sqwk += ((s[1] & 0x01) << 2) | ((s[1] & 0x04) >> 1) | ((s[1] & 0x10) >> 4); // D
+
+ log_entry.sqwk = entry.sqwk = sqwk;
+ }
+
+ if (df == 20 || df == 21) {
+ if (raw_data[4] == 0x20) { // try decode as BDS20
+ std::string callsign = decode_frame_id(frame);
+ if (callsign.find('#') == std::string::npos) { // all chars OK
+ entry.set_callsign(callsign);
+ log_entry.callsign = callsign;
+ }
+ }
+ }
+
+ logger->log(log_entry);
}
void ADSBRxView::on_tick_second() {
@@ -544,7 +654,7 @@ void ADSBRxView::refresh_ui() {
}
// NB: current entry also gets a marker so it shows up if map is panned.
- if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
+ if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
diff --git a/firmware/application/apps/ui_adsb_rx.hpp b/firmware/application/apps/ui_adsb_rx.hpp
index 9ef42718a..039135f5d 100644
--- a/firmware/application/apps/ui_adsb_rx.hpp
+++ b/firmware/application/apps/ui_adsb_rx.hpp
@@ -95,8 +95,8 @@ struct AircraftRecentEntry {
ADSBAgeState state{ADSBAgeState::Invalid};
uint32_t age{0}; // In seconds
uint32_t amp{0};
- adsb_pos pos{false, 0, 0, 0};
- adsb_vel velo{false, 0, 999, 0};
+ adsb_pos pos{false, false, 0, 0, 0};
+ adsb_vel velo{false, SPD_GND, 0, 999, 0};
ADSBFrame frame_pos_even{};
ADSBFrame frame_pos_odd{};
@@ -105,6 +105,7 @@ struct AircraftRecentEntry {
std::string info_string{};
uint8_t sil{0}; // Surveillance integrity level
+ uint16_t sqwk{0};
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
@@ -152,8 +153,8 @@ struct AircraftRecentEntry {
age += delta;
if (age < ADSBAgeLimit::Current)
- state = pos.valid ? ADSBAgeState::Invalid
- : ADSBAgeState::Current;
+ state = pos.pos_valid ? ADSBAgeState::Current
+ : ADSBAgeState::Invalid;
else if (age < ADSBAgeLimit::Recent)
state = ADSBAgeState::Recent;
@@ -178,6 +179,7 @@ struct ADSBLogEntry {
adsb_vel vel{};
uint8_t vel_type{};
uint8_t sil{};
+ uint16_t sqwk{};
};
// TODO: Make logging optional.
@@ -228,7 +230,7 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_model{
- {0 * 8, 6 * 16, 30 * 8, 16},
+ {0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_type{
@@ -236,19 +238,19 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_number_of_engines{
- {18 * 8, 8 * 16, 30 * 8, 16},
+ {18 * 8, 8 * 16, screen_width, 16},
"-"};
Text text_engine_type{
- {0 * 8, 10 * 16, 30 * 8, 16},
+ {0 * 8, 10 * 16, screen_width, 16},
"-"};
Text text_owner{
- {0 * 8, 12 * 16, 30 * 8, 16},
+ {0 * 8, 12 * 16, screen_width, 16},
"-"};
Text text_operator{
- {0 * 8, 14 * 16, 30 * 8, 16},
+ {0 * 8, 14 * 16, screen_width, 16},
"-"};
Button button_close{
@@ -310,7 +312,7 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_airline{
- {0 * 8, 4 * 16, 30 * 8, 16},
+ {0 * 8, 4 * 16, screen_width, 16},
"-"};
Text text_country{
@@ -318,18 +320,18 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_infos{
- {0 * 8, 6 * 16, 30 * 8, 16},
+ {0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_info2{
- {0 * 8, 7 * 16, 30 * 8, 16},
+ {0 * 8, 7 * 16, screen_width, 16},
"-"};
Text text_frame_pos_even{
- {0 * 8, 14 * 16, 30 * 8, 16},
+ {0 * 8, 14 * 16, screen_width, 16},
"-"};
Text text_frame_pos_odd{
- {0 * 8, 16 * 16, 30 * 8, 16},
+ {0 * 8, 16 * 16, screen_width, 16},
"-"};
Button button_aircraft_details{
@@ -440,7 +442,7 @@ class ADSBRxView : public View {
};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
diff --git a/firmware/application/apps/ui_aprs_rx.cpp b/firmware/application/apps/ui_aprs_rx.cpp
index d5ff4f238..e24fdcdc3 100644
--- a/firmware/application/apps/ui_aprs_rx.cpp
+++ b/firmware/application/apps/ui_aprs_rx.cpp
@@ -99,13 +99,21 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
field_frequency.set_value(aprs_rx_freq);
} else if (i == 1) { // NA - North America - is also the default
field_frequency.set_value(144390000);
- } else if (i == 2) { // EUR
- field_frequency.set_value(144800000);
- } else if (i == 3) { // AUS
- field_frequency.set_value(145175000);
- } else if (i == 4) { // NZ
+ } else if (i == 2) { // NZ
field_frequency.set_value(144575000);
- } else if (i == 5) { // ISS
+ } else if (i == 3) { // JAP
+ field_frequency.set_value(144640000);
+ } else if (i == 4) { // PHI
+ field_frequency.set_value(144740000);
+ } else if (i == 5) { // EUR
+ field_frequency.set_value(144800000);
+ } else if (i == 6) { // THA
+ field_frequency.set_value(144900000);
+ } else if (i == 7) { // AUS
+ field_frequency.set_value(145175000);
+ } else if (i == 8) { // BR
+ field_frequency.set_value(145570000);
+ } else if (i == 9) { // ISS
field_frequency.set_value(145825000);
}
options_region_id = i;
@@ -239,8 +247,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
details_view.hidden(true);
- recent_entries_view.set_parent_rect({0, 0, 240, 280});
- details_view.set_parent_rect({0, 0, 240, 280});
+ recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
+ details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
this->on_show_detail(entry);
diff --git a/firmware/application/apps/ui_aprs_rx.hpp b/firmware/application/apps/ui_aprs_rx.hpp
index 6761842d9..41a55f2cf 100644
--- a/firmware/application/apps/ui_aprs_rx.hpp
+++ b/firmware/application/apps/ui_aprs_rx.hpp
@@ -134,7 +134,7 @@ class APRSDetailsView : public View {
bool send_updates{false};
Console console{
- {0, 0 * 16, 240, 224}};
+ {0, 0 * 16, screen_width, 224}};
Button button_done{
{160, 14 * 16, 8 * 8, 3 * 16},
@@ -220,24 +220,28 @@ class APRSRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
3,
{{"MAN", 0},
{"NA ", 1},
- {"EUR", 2},
- {"AUS", 3},
- {"NZ ", 4},
- {"ISS", 5}}};
+ {"NZ ", 2},
+ {"JAP", 3},
+ {"PHI", 4},
+ {"EUR", 5},
+ {"THA", 6},
+ {"AUS", 7},
+ {"BR ", 8},
+ {"ISS", 9}}};
FrequencyField field_frequency{
{3 * 8, 0 * 16}};
// DEBUG
RecordView record_view{
- {0 * 8, 1 * 16, 30 * 8, 1 * 16},
+ {0 * 8, 1 * 16, screen_width, 1 * 16},
u"AFS_????.WAV",
aprs_dir,
RecordView::FileType::WAV,
@@ -245,7 +249,7 @@ class APRSRxView : public View {
4};
Console console{
- {0, 2 * 16, 240, 240}};
+ {0, 2 * 16, screen_width, screen_height - 80}};
std::unique_ptr logger{};
};
@@ -261,7 +265,7 @@ class APRSRXView : public View {
private:
NavigationView& nav_;
- Rect view_rect = {0, 3 * 8, 240, 280};
+ Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
APRSRxView view_stream{nav_, view_rect};
APRSTableView view_table{nav_, view_rect};
diff --git a/firmware/application/apps/ui_aprs_tx.cpp b/firmware/application/apps/ui_aprs_tx.cpp
index e5c6c79ed..021104437 100644
--- a/firmware/application/apps/ui_aprs_tx.cpp
+++ b/firmware/application/apps/ui_aprs_tx.cpp
@@ -94,6 +94,7 @@ APRSTXView::APRSTXView(NavigationView& nav) {
nav,
payload,
30,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
text_payload.set(s);
});
diff --git a/firmware/application/apps/ui_aprs_tx.hpp b/firmware/application/apps/ui_aprs_tx.hpp
index 1cf123101..014c3b9cd 100644
--- a/firmware/application/apps/ui_aprs_tx.hpp
+++ b/firmware/application/apps/ui_aprs_tx.hpp
@@ -91,7 +91,7 @@ class APRSTXView : public View {
' '};
Text text_payload{
- {0 * 8, 5 * 16, 30 * 8, 16},
+ {0 * 8, 5 * 16, screen_width, 16},
"-"};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
diff --git a/firmware/application/apps/ui_battinfo.hpp b/firmware/application/apps/ui_battinfo.hpp
index 45960c2e4..4628f6b3b 100644
--- a/firmware/application/apps/ui_battinfo.hpp
+++ b/firmware/application/apps/ui_battinfo.hpp
@@ -90,7 +90,7 @@ class BattinfoView : public View {
"-"};
Text text_warn{
- {1 * 8, 8 * 16, 30 * 8, 2 * 16},
+ {1 * 8, 8 * 16, screen_width, 2 * 16},
""}; */
Button button_mode{
diff --git a/firmware/application/apps/ui_bht_tx.hpp b/firmware/application/apps/ui_bht_tx.hpp
index 0aecd2915..71008bf31 100644
--- a/firmware/application/apps/ui_bht_tx.hpp
+++ b/firmware/application/apps/ui_bht_tx.hpp
@@ -189,7 +189,7 @@ class BHTView : public View {
tx_modes tx_mode = IDLE;
- Rect view_rect = {0, 3 * 8, 240, 176};
+ Rect view_rect = {0, 3 * 8, screen_width, 176};
XylosView view_xylos{view_rect};
EPARView view_EPAR{view_rect};
@@ -218,7 +218,7 @@ class BHTView : public View {
' '};
ProgressBar progressbar{
- {0 * 8, 29 * 8, 30 * 8, 16},
+ {0 * 8, 29 * 8, screen_width, 16},
};
TransmitterView tx_view{
diff --git a/firmware/application/apps/ui_bmp_file_viewer.hpp b/firmware/application/apps/ui_bmp_file_viewer.hpp
index c9bd57c41..9afc6bbe3 100644
--- a/firmware/application/apps/ui_bmp_file_viewer.hpp
+++ b/firmware/application/apps/ui_bmp_file_viewer.hpp
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
private:
NavigationView& nav_;
std::filesystem::path path_{};
- BMPViewer bmp{{0, 0, 240, 320}};
+ BMPViewer bmp{{0, 0, screen_width, screen_height}};
};
} // namespace ui
diff --git a/firmware/application/apps/ui_btle_rx.hpp b/firmware/application/apps/ui_btle_rx.hpp
index 963bfa1bf..70f4edc93 100644
--- a/firmware/application/apps/ui_btle_rx.hpp
+++ b/firmware/application/apps/ui_btle_rx.hpp
@@ -78,11 +78,11 @@ class BTLERxView : public View {
nav_};
Button button_modem_setup{
- {240 - 12 * 8, 1 * 16, 96, 24},
+ {screen_width - 12 * 8, 1 * 16, 96, 24},
"Modem setup"};
Console console{
- {0, 4 * 16, 240, 240}};
+ {0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp
index daa986f43..b12ce79f0 100644
--- a/firmware/application/apps/ui_debug.cpp
+++ b/firmware/application/apps/ui_debug.cpp
@@ -202,10 +202,10 @@ RegistersView::RegistersView(
};
button_done.on_select = [&nav](Button&) { nav.pop(); };
- registers_widget.set_parent_rect({0, 48, 240, 192});
+ registers_widget.set_parent_rect({0, 48, screen_width, 192});
registers_widget.set_page(0);
- text_title.set_parent_rect({(240 - static_cast(title.size()) * 8) / 2, 16,
+ text_title.set_parent_rect({(screen_width - static_cast(title.size()) * 8) / 2, 16,
static_cast(title.size()) * 8, 16});
text_title.set(title);
@@ -419,7 +419,6 @@ void DebugMenuView::on_populate() {
}
add_items({
{"Buttons Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_controls, [this]() { nav_.push(); }},
- {"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push(); }},
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }},
@@ -483,7 +482,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({®isters_widget, &text_checksum, &text_checksum2, &button_ok});
- registers_widget.set_parent_rect({0, 32, 240, 192});
+ registers_widget.set_parent_rect({0, 32, screen_width, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp
index 6c7b0c43b..519d94f2f 100644
--- a/firmware/application/apps/ui_debug.hpp
+++ b/firmware/application/apps/ui_debug.hpp
@@ -283,7 +283,7 @@ class DebugMemoryDumpView : public View {
"Write"};
Button button_done{
- {128, 240, 96, 24},
+ {128, screen_height - 80, 96, 24},
"Done"};
Labels labels{
@@ -332,7 +332,7 @@ class DebugPmemView : public View {
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
- {240 / 3, 270, 240 / 3, 24},
+ {screen_width / 3, 270, screen_width / 3, 24},
"OK",
};
@@ -364,7 +364,7 @@ public:
private:
Console console {
- { 8, 16, 224, 240 }
+ { 8, 16, 224, screen_height-80 }
};
Button button_exit {
diff --git a/firmware/application/apps/ui_encoders.hpp b/firmware/application/apps/ui_encoders.hpp
index 39879d490..7c32584aa 100644
--- a/firmware/application/apps/ui_encoders.hpp
+++ b/firmware/application/apps/ui_encoders.hpp
@@ -25,7 +25,6 @@
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "encoders.hpp"
-#include "de_bruijn.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
@@ -125,7 +124,7 @@ class EncodersConfigView : public View {
""};
Waveform waveform{
- {0, 17 * 8, 240, 32},
+ {0, 17 * 8, screen_width, 32},
waveform_buffer,
0,
0,
@@ -202,7 +201,7 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
- Rect view_rect = {0, 4 * 8, 240, 168};
+ Rect view_rect = {0, 4 * 8, screen_width, 168};
EncodersConfigView view_config{nav_, view_rect};
EncodersScanView view_scan{nav_, view_rect};
diff --git a/firmware/application/apps/ui_external_module_view.cpp b/firmware/application/apps/ui_external_module_view.cpp
index 5e376981b..b3c0e0559 100644
--- a/firmware/application/apps/ui_external_module_view.cpp
+++ b/firmware/application/apps/ui_external_module_view.cpp
@@ -92,12 +92,21 @@ void ExternalModuleView::on_tick_second() {
case app_location_t::TX:
btnText += " (TX)";
break;
+ case app_location_t::TRX:
+ btnText += " (TRX)";
+ break;
+ case app_location_t::SETTINGS:
+ btnText += " (Settings)";
+ break;
case app_location_t::DEBUG:
btnText += " (Debug)";
break;
case app_location_t::HOME:
btnText += " (Home)";
break;
+ case app_location_t::GAMES:
+ btnText += " (Games)";
+ break;
}
switch (i) {
@@ -119,4 +128,5 @@ void ExternalModuleView::on_tick_second() {
}
}
}
+
} // namespace ui
diff --git a/firmware/application/apps/ui_external_module_view.hpp b/firmware/application/apps/ui_external_module_view.hpp
index f09cf2f28..f91cd4541 100644
--- a/firmware/application/apps/ui_external_module_view.hpp
+++ b/firmware/application/apps/ui_external_module_view.hpp
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
Text text_app5_name{{24, 160, 200, 16}};
Button dummy{
- {240, 0, 0, 0},
+ {screen_width, 0, 0, 0},
""};
SignalToken signal_token_tick_second{};
diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp
index bcc80fd8d..af156f8d6 100644
--- a/firmware/application/apps/ui_fileman.cpp
+++ b/firmware/application/apps/ui_fileman.cpp
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
+ // Collect all entries first
+ std::list all_entries;
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (!show_hidden_files && is_hidden_file(entry.path()))
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
- insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
+ insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
- insert_sorted(entry_list, {entry.path().string(), 0, true});
+ insert_sorted(all_entries, {entry.path().string(), 0, true});
}
}
- // paginating
- auto list_size = entry_list.size();
- nb_pages = 1 + (list_size / items_per_page);
- size_t start = pagination * items_per_page;
- size_t stop = start + items_per_page;
- if (list_size > start) {
- if (list_size < stop)
- stop = list_size;
- entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
- entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
+ // Calculate pagination
+ nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
+ if (nb_pages == 0) nb_pages = 1;
+
+ size_t start_idx = pagination * items_per_page;
+ size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
+
+ // Add "parent" directory if not at the root and on first page
+ if (!dir_path.empty() && pagination == 0) {
+ entry_list.push_back({parent_dir_path.string(), 0, true});
}
- // Add "parent" directory if not at the root.
- if (!dir_path.empty() && pagination == 0)
- entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
-
- // add next page
- if (list_size > start + items_per_page) {
- entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
- }
-
- // add prev page
+ // Add prev page navigation if not on first page
if (pagination > 0) {
- entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
+ entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
+ }
+
+ // Add entries for current page
+ auto it = all_entries.begin();
+ std::advance(it, start_idx);
+
+ for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
+ entry_list.push_back(*it);
+ }
+
+ // Add next page navigation if not on last page
+ if (end_idx < all_entries.size()) {
+ entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
}
@@ -320,30 +326,49 @@ void FileManBaseView::focus() {
} else {
menu_view.focus();
}
+
+ // Set menu to the correct page and select the correct item
+ menu_view.set_highlighted(prev_highlight % items_per_page);
}
+// Push directory - store the global index (page * items_per_page + local_index)
void FileManBaseView::push_dir(const fs::path& path) {
if (path == parent_dir_path) {
pop_dir();
} else {
+ // Save global index (combines page number and item position)
+ saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
+
current_path /= path;
- saved_index_stack.push_back(menu_view.highlighted_index());
- menu_view.set_highlighted(0);
- reload_current(true);
+ reload_current(true); // Reset pagination when entering new directory
}
}
void FileManBaseView::pop_dir() {
- if (saved_index_stack.empty())
+ if (current_path.empty()) {
return;
+ }
+ // Move to parent directory
current_path = current_path.parent_path();
- reload_current(true);
- menu_view.set_highlighted(saved_index_stack.back());
- saved_index_stack.pop_back();
+
+ // Restore the previous global index if available
+ if (!saved_index_stack.empty()) {
+ uint32_t global_index = saved_index_stack.back();
+ saved_index_stack.pop_back();
+
+ // Calculate pagination from global index
+ pagination = global_index / items_per_page;
+
+ // Calculate local index within the page
+ prev_highlight = global_index % items_per_page;
+ restoring_navigation = true;
+ }
+
+ reload_current(false); // Important: don't reset pagination
}
-std::string get_extension(std::string t) {
+std::string FileManBaseView::get_extension(const std::string& t) const {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
@@ -372,7 +397,9 @@ void FileManBaseView::refresh_list() {
if (on_refresh_widgets)
on_refresh_widgets(false);
- prev_highlight = menu_view.highlighted_index();
+ if (!restoring_navigation) {
+ prev_highlight = menu_view.highlighted_index();
+ }
menu_view.clear();
for (const auto& entry : entry_list) {
@@ -411,14 +438,29 @@ void FileManBaseView::refresh_list() {
}
menu_view.set_highlighted(prev_highlight);
+ restoring_navigation = false;
}
void FileManBaseView::reload_current(bool reset_pagination) {
- if (reset_pagination) pagination = 0;
+ // Only reset pagination if explicitly requested
+ if (reset_pagination) {
+ pagination = 0;
+ }
load_directory_contents(current_path);
refresh_list();
}
+void FileManBaseView::copy_waterfall(std::filesystem::path path) {
+ nav_.push(
+ "Install", " Use this gradient file\n for all waterfalls?", YESNO,
+ [this, path](bool choice) {
+ if (choice) {
+ delete_file(default_gradient_file);
+ copy_file(path, default_gradient_file);
+ }
+ });
+}
+
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
const fs::path& ext) const {
size_t index = 0;
@@ -444,7 +486,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
- menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
+ menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
refresh_list();
@@ -478,63 +520,6 @@ void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
-/* FileSaveView **************************************************************/
-/*
-FileSaveView::FileSaveView(
- NavigationView& nav,
- const fs::path& path,
- const fs::path& file
-) : nav_{ nav },
- path_{ path },
- file_{ file }
-{
- add_children({
- &text_path,
- &button_edit_path,
- &text_name,
- &button_edit_name,
- &button_save,
- &button_cancel,
- });
-
- button_edit_path.on_select = [this](Button&) {
- buffer_ = path_.string();
- text_prompt(nav_, buffer_, max_filename_length,
- [this](std::string&) {
- path_ = buffer_;
- refresh_widgets();
- });
- };
-
- button_edit_name.on_select = [this](Button&) {
- buffer_ = file_.string();
- text_prompt(nav_, buffer_, max_filename_length,
- [this](std::string&) {
- file_ = buffer_;
- refresh_widgets();
- });
- };
-
- button_save.on_select = [this](Button&) {
- if (on_save)
- on_save(path_ / file_);
- else
- nav_.pop();
- };
-
- button_cancel.on_select = [this](Button&) {
- nav_.pop();
- };
-
- refresh_widgets();
-}
-
-void FileSaveView::refresh_widgets() {
- text_path.set(truncate(path_, 30));
- text_name.set(truncate(file_, 30));
- set_dirty();
-}
-*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
@@ -566,7 +551,7 @@ void FileManagerView::on_rename(std::string_view hint) {
cursor_pos = pos;
text_prompt(
- nav_, name_buffer, cursor_pos, max_filename_length,
+ nav_, name_buffer, cursor_pos, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
@@ -640,7 +625,7 @@ void FileManagerView::on_clean() {
void FileManagerView::on_new_dir() {
name_buffer = "";
- text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
+ text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& dir_name) {
make_new_directory(current_path / dir_name);
reload_current(true);
});
@@ -671,7 +656,7 @@ void FileManagerView::on_paste() {
void FileManagerView::on_new_file() {
name_buffer = "";
- text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
+ text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current(true);
});
@@ -685,7 +670,11 @@ bool FileManagerView::handle_file_open() {
auto ext = path.extension();
if (path_iequal(txt_ext, ext)) {
- nav_.push(path);
+ if (path_iequal(current_path, u"/" + waterfalls_dir)) {
+ copy_waterfall(path);
+ } else {
+ nav_.push(path);
+ }
return true;
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
diff --git a/firmware/application/apps/ui_fileman.hpp b/firmware/application/apps/ui_fileman.hpp
index e044e53ec..892fe0914 100644
--- a/firmware/application/apps/ui_fileman.hpp
+++ b/firmware/application/apps/ui_fileman.hpp
@@ -64,6 +64,7 @@ class FileManBaseView : public View {
uint32_t prev_highlight = 0;
uint8_t pagination = 0;
uint8_t nb_pages = 1;
+ bool restoring_navigation = false;
static constexpr size_t max_filename_length = 20;
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
static constexpr size_t items_per_page = 20;
@@ -96,6 +97,8 @@ class FileManBaseView : public View {
void load_directory_contents(const std::filesystem::path& dir_path);
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
+ std::string get_extension(const std::string& t) const;
+ void copy_waterfall(std::filesystem::path path);
NavigationView& nav_;
@@ -124,7 +127,7 @@ class FileManBaseView : public View {
};
MenuView menu_view{
- {0, 2 * 8, 240, 26 * 8},
+ {0, 2 * 8, screen_width, 26 * 8},
true};
Button button_exit{
@@ -171,7 +174,7 @@ private:
};
Text text_path {
- { 0 * 8, 2 * 16, 30 * 8, 16 },
+ { 0 * 8, 2 * 16, screen_width, 16 },
"",
};
@@ -181,7 +184,7 @@ private:
};
Text text_name {
- { 0 * 8, 7 * 16, 30 * 8, 16 },
+ { 0 * 8, 7 * 16, screen_width, 16 },
"",
};
diff --git a/firmware/application/apps/ui_flash_utility.cpp b/firmware/application/apps/ui_flash_utility.cpp
index 55d5a23bd..64c10f618 100644
--- a/firmware/application/apps/ui_flash_utility.cpp
+++ b/firmware/application/apps/ui_flash_utility.cpp
@@ -80,7 +80,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
add_children({&labels,
&menu_view});
- menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
+ menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(apps_dir);
ensure_directory(firmware_dir);
@@ -177,4 +177,4 @@ void FlashUtilityView::focus() {
menu_view.focus();
}
-} /* namespace ui */
\ No newline at end of file
+} /* namespace ui */
diff --git a/firmware/application/apps/ui_flash_utility.hpp b/firmware/application/apps/ui_flash_utility.hpp
index 626a4ff40..6b623bc34 100644
--- a/firmware/application/apps/ui_flash_utility.hpp
+++ b/firmware/application/apps/ui_flash_utility.hpp
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
- {0, 2 * 8, 240, 26 * 8},
+ {0, 2 * 8, screen_width, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp
index 7e230dc60..e543fa00e 100644
--- a/firmware/application/apps/ui_freqman.cpp
+++ b/firmware/application/apps/ui_freqman.cpp
@@ -45,8 +45,8 @@ using option_db_t = std::pair;
using options_db_t = std::vector;
extern options_db_t freqman_modulations;
-extern options_db_t freqman_bandwidths[4];
-extern options_db_t freqman_steps;
+extern options_db_t freqman_bandwidths[6];
+// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
extern options_db_t freqman_steps_short;
options_t dboptions_to_options(const options_db_t& dboptions) {
@@ -240,7 +240,7 @@ void FrequencyManagerView::on_edit_freq() {
void FrequencyManagerView::on_edit_desc() {
temp_buffer_ = current_entry().description;
- text_prompt(nav_, temp_buffer_, freqman_max_desc_size, [this](std::string& new_desc) {
+ text_prompt(nav_, temp_buffer_, freqman_max_desc_size, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_desc) {
auto entry = current_entry();
entry.description = std::move(new_desc);
db_.replace_entry(current_index(), entry);
@@ -250,7 +250,7 @@ void FrequencyManagerView::on_edit_desc() {
void FrequencyManagerView::on_add_category() {
temp_buffer_.clear();
- text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
+ text_prompt(nav_, temp_buffer_, 20, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
if (!new_name.empty()) {
create_freqman_file(new_name);
refresh_categories();
diff --git a/firmware/application/apps/ui_freqman.hpp b/firmware/application/apps/ui_freqman.hpp
index 5392819df..8877a30a3 100644
--- a/firmware/application/apps/ui_freqman.hpp
+++ b/firmware/application/apps/ui_freqman.hpp
@@ -100,7 +100,7 @@ class FrequencySaveView : public FreqManBaseView {
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
TextField field_description{
- {0 * 8, 7 * 16, 30 * 8, 1 * 16},
+ {0 * 8, 7 * 16, screen_width, 1 * 16},
""};
Button button_save{
diff --git a/firmware/application/apps/ui_iq_trim.cpp b/firmware/application/apps/ui_iq_trim.cpp
index 44583d650..d5cbffd4d 100644
--- a/firmware/application/apps/ui_iq_trim.cpp
+++ b/firmware/application/apps/ui_iq_trim.cpp
@@ -34,6 +34,7 @@ namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
+ power_buckets_.resize(screen_width);
add_children({
&labels,
&field_path,
diff --git a/firmware/application/apps/ui_iq_trim.hpp b/firmware/application/apps/ui_iq_trim.hpp
index dd990f4a0..03d33a5e9 100644
--- a/firmware/application/apps/ui_iq_trim.hpp
+++ b/firmware/application/apps/ui_iq_trim.hpp
@@ -96,7 +96,7 @@ class IQTrimView : public View {
std::filesystem::path path_{};
Optional info_{};
- std::array power_buckets_{};
+ std::vector power_buckets_{};
TrimProgressUI progress_ui{};
Labels labels{
@@ -112,7 +112,7 @@ class IQTrimView : public View {
};
TextField field_path{
- {0 * 8, 1 * 16, 30 * 8, 1 * 16},
+ {0 * 8, 1 * 16, screen_width, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
diff --git a/firmware/application/apps/ui_looking_glass_app.cpp b/firmware/application/apps/ui_looking_glass_app.cpp
index 5e412c420..187c5f75c 100644
--- a/firmware/application/apps/ui_looking_glass_app.cpp
+++ b/firmware/application/apps/ui_looking_glass_app.cpp
@@ -94,10 +94,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
- // starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
- freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
+ // starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
+ freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
} else
- freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
+ freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
return freq_at_pos;
}
@@ -122,15 +122,15 @@ void GlassView::reset_live_view() {
// Clear screen in peak mode.
if (live_frequency_view == 2)
- display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
+ display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
- spectrum_row[pixel_index] = spectrum_rgb3_lut[power]; // row of colors
+ spectrum_row[pixel_index] = gradient.lut[power]; // row of colors
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
pixel_index++;
- if (pixel_index == SCREEN_W) // got an entire waterfall line
+ if (pixel_index == screen_width) // got an entire waterfall line
{
if (live_frequency_view > 0) {
constexpr int rssi_sample_range = SPEC_NB_BINS;
@@ -140,22 +140,22 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
constexpr int raw_delta = raw_max - raw_min;
- const range_t y_max_range{0, 320 - (108 + 16)};
+ const range_t y_max_range{0, screen_height - (108 + 16)};
// drawing and keeping track of max freq
- for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
+ for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = get_freq_from_bin_pos(xpos);
}
- int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
+ int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
if (live_frequency_view != 2) {
- display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
+ display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
}
- display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
+ display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
@@ -163,7 +163,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
plot_marker(marker_pixel_index);
} else {
- display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
+ display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
}
pixel_index = 0; // Start New cascade line
}
@@ -199,7 +199,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
- // we actually need SCREEN_W (240) of those bins
+ // we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
@@ -238,7 +238,7 @@ void GlassView::on_hide() {
}
void GlassView::on_show() {
- display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
+ display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
baseband::spectrum_streaming_start();
}
@@ -253,11 +253,11 @@ void GlassView::on_range_changed() {
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 2;
- bin_length = SCREEN_W;
+ bin_length = screen_width;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_range;
- each_bin_size = looking_glass_bandwidth / SCREEN_W;
+ each_bin_size = looking_glass_bandwidth / screen_width;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else {
@@ -269,7 +269,7 @@ void GlassView::on_range_changed() {
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
- bin_length = SCREEN_W;
+ bin_length = screen_width;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
@@ -279,7 +279,7 @@ void GlassView::on_range_changed() {
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
- marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
+ marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
pixel_index = 0;
max_power = 0;
@@ -304,10 +304,10 @@ void GlassView::plot_marker(uint8_t pos) {
{
shift_y = 16;
}
- portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
- portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
- portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
- portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
+ portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
+ portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
+ portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
+ portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
}
void GlassView::update_min(int32_t v) {
@@ -358,6 +358,11 @@ GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
+ spectrum_row.resize(screen_width);
+ spectrum_data.resize(screen_width);
+ if (!gradient.load_file(default_gradient_file)) {
+ gradient.set_default();
+ }
add_children({&labels,
&field_frequency_min,
@@ -433,11 +438,11 @@ GlassView::GlassView(
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
- display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
+ display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
break;
case 1: // LEVEL
- display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
+ display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -447,7 +452,7 @@ GlassView::GlassView(
case 2: // PEAK
default:
- display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
+ display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -487,9 +492,9 @@ GlassView::GlassView(
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
- marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
- else if ((marker_pixel_index + delta) > SCREEN_W)
- marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
+ marker_pixel_index = marker_pixel_index + delta + screen_width;
+ else if ((marker_pixel_index + delta) > screen_width)
+ marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
@@ -527,7 +532,7 @@ GlassView::GlassView(
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
- display.scroll_set_area(109, 319);
+ display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
// trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
@@ -535,7 +540,7 @@ GlassView::GlassView(
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, trigger);
- marker_pixel_index = SCREEN_W / 2;
+ marker_pixel_index = screen_width / 2;
on_range_changed(); // Force a UI update.
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
diff --git a/firmware/application/apps/ui_looking_glass_app.hpp b/firmware/application/apps/ui_looking_glass_app.hpp
index d02803b9e..9ddfd18fc 100644
--- a/firmware/application/apps/ui_looking_glass_app.hpp
+++ b/firmware/application/apps/ui_looking_glass_app.hpp
@@ -35,7 +35,7 @@
#include "ui_receiver.hpp"
#include "string_format.hpp"
#include "analog_audio_app.hpp"
-#include "spectrum_color_lut.hpp"
+#include "gradient.hpp"
namespace ui {
@@ -51,9 +51,6 @@ namespace ui {
#define LOOKING_GLASS_SINGLEPASS 2
// one spectrum line number of bins
#define SPEC_NB_BINS 256
-// screen dimensions
-#define SCREEN_W 240
-#define SCREEN_H 320
class GlassView : public View {
public:
@@ -74,6 +71,7 @@ class GlassView : public View {
private:
NavigationView& nav_;
+ Gradient gradient{};
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
// Settings
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
@@ -154,8 +152,8 @@ class GlassView : public View {
uint8_t min_color_power{0}; // Filter cutoff level.
uint32_t pixel_index{0};
- std::array spectrum_row{};
- std::array spectrum_data{};
+ std::vector spectrum_row{};
+ std::vector spectrum_data{};
ChannelSpectrumFIFO* fifo{};
int32_t steps = 1;
@@ -167,7 +165,7 @@ class GlassView : public View {
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
int16_t max_freq_power = -1000;
- uint8_t bin_length = SCREEN_W;
+ uint8_t bin_length = screen_width;
uint8_t offset = 0;
uint8_t ignore_dc = 0;
@@ -221,7 +219,7 @@ class GlassView : public View {
{}};
ButtonWithEncoder button_beep_squelch{
- {240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
+ {screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
@@ -292,15 +290,15 @@ class GlassView : public View {
}};
Button button_jump{
- {SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
+ {screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
"JMP"};
Button button_rst{
- {SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
+ {screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
"RST"};
Text freq_stats{
- {0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
+ {0 * 8, 5 * 16, screen_width - 10 * 8, 8},
""};
MessageHandlerRegistration message_handler_spectrum_config{
diff --git a/firmware/application/apps/ui_mictx.cpp b/firmware/application/apps/ui_mictx.cpp
index dfa80a65a..62455e63d 100644
--- a/firmware/application/apps/ui_mictx.cpp
+++ b/firmware/application/apps/ui_mictx.cpp
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
- case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
+ case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
- receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
+ receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
diff --git a/firmware/application/apps/ui_mictx.hpp b/firmware/application/apps/ui_mictx.hpp
index 6b61a8f0b..87c8610f4 100644
--- a/firmware/application/apps/ui_mictx.hpp
+++ b/firmware/application/apps/ui_mictx.hpp
@@ -350,7 +350,7 @@ class MicTXView : public View {
};
Button tx_button{
- {10 * 8, 30 * 8, 10 * 8, 5 * 8},
+ {10 * 8, screen_width, 10 * 8, 5 * 8},
"PTT TX",
true};
diff --git a/firmware/application/apps/ui_playlist.hpp b/firmware/application/apps/ui_playlist.hpp
index 72b50d7b3..cf98e6c24 100644
--- a/firmware/application/apps/ui_playlist.hpp
+++ b/firmware/application/apps/ui_playlist.hpp
@@ -107,7 +107,7 @@ class PlaylistView : public View {
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
- {0 * 8, 0 * 16, 30 * 8, 16}};
+ {0 * 8, 0 * 16, screen_width, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
@@ -139,13 +139,13 @@ class PlaylistView : public View {
true};
ImageButton button_play{
- {28 * 8, 2 * 16, 2 * 8, 1 * 16},
+ {screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Text text_track{
- {0 * 8, 3 * 16, 30 * 8, 16}};
+ {0 * 8, 3 * 16, screen_width, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
diff --git a/firmware/application/apps/ui_pocsag_tx.cpp b/firmware/application/apps/ui_pocsag_tx.cpp
index 2efed2653..b71aca63f 100644
--- a/firmware/application/apps/ui_pocsag_tx.cpp
+++ b/firmware/application/apps/ui_pocsag_tx.cpp
@@ -153,7 +153,7 @@ void POCSAGTXView::paint(Painter&) {
}
void POCSAGTXView::on_set_text(NavigationView& nav) {
- text_prompt(nav, buffer, MAX_POCSAG_LENGTH);
+ text_prompt(nav, buffer, MAX_POCSAG_LENGTH, ENTER_KEYBOARD_MODE_ALPHA);
}
POCSAGTXView::POCSAGTXView(
diff --git a/firmware/application/apps/ui_pocsag_tx.hpp b/firmware/application/apps/ui_pocsag_tx.hpp
index f4eb242ca..b23e27dd8 100644
--- a/firmware/application/apps/ui_pocsag_tx.hpp
+++ b/firmware/application/apps/ui_pocsag_tx.hpp
@@ -122,10 +122,10 @@ class POCSAGTXView : public View {
}};
Text text_message{
- {0 * 8, 16 * 8, 30 * 8, 16},
+ {0 * 8, 16 * 8, screen_width, 16},
""};
Text text_message_l2{
- {0 * 8, 18 * 8, 30 * 8, 16},
+ {0 * 8, 18 * 8, screen_width, 16},
""};
Button button_message{
diff --git a/firmware/application/apps/ui_rds.cpp b/firmware/application/apps/ui_rds.cpp
index a17e7a59a..121023e2d 100644
--- a/firmware/application/apps/ui_rds.cpp
+++ b/firmware/application/apps/ui_rds.cpp
@@ -65,6 +65,7 @@ RDSPSNView::RDSPSNView(
nav,
PSN,
8,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
text_psn.set(s);
});
@@ -86,6 +87,7 @@ RDSRadioTextView::RDSRadioTextView(
nav,
radiotext,
28,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
text_radiotext.set(s);
});
diff --git a/firmware/application/apps/ui_rds.hpp b/firmware/application/apps/ui_rds.hpp
index aebd8deb4..40e9d285f 100644
--- a/firmware/application/apps/ui_rds.hpp
+++ b/firmware/application/apps/ui_rds.hpp
@@ -160,7 +160,7 @@ class RDSView : public View {
void start_tx();
- Rect view_rect = {0, 8 * 8, 240, 192};
+ Rect view_rect = {0, 8 * 8, screen_width, 192};
RDSPSNView view_PSN{nav_, view_rect};
RDSRadioTextView view_radiotext{nav_, view_rect};
diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp
index 977f9cbbb..3126c9a29 100644
--- a/firmware/application/apps/ui_recon.cpp
+++ b/firmware/application/apps/ui_recon.cpp
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
tx_view.hidden(true);
// set record View
- record_view = std::make_unique(Rect{0, 0, 30 * 8, 1 * 16},
+ record_view = std::make_unique(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -517,11 +517,7 @@ ReconView::ReconView(NavigationView& nav)
};
set_loop_config(continuous);
- rssi.set_focusable(true);
rssi.set_peak(true, 500);
- rssi.on_select = [this](RSSI&) {
- nav_.replace();
- };
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
button_mic_app.on_select = [this](Button&) {
@@ -1168,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
if (new_mod == SPEC_MODULATION) {
if (persistent_memory::recon_repeat_recorded()) {
- record_view = std::make_unique(Rect{0, 0, 30 * 8, 1 * 16},
+ record_view = std::make_unique(Rect{0, 0, screen_width, 1 * 16},
u"RECON_REPEAT.C16", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else {
- record_view = std::make_unique(Rect{0, 0, 30 * 8, 1 * 16},
+ record_view = std::make_unique(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_RAW", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_date_frequency(true);
}
} else {
- record_view = std::make_unique(Rect{0, 0, 30 * 8, 1 * 16},
+ record_view = std::make_unique(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1257,9 +1253,17 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
field_mode.set_selected_index(new_mod);
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
- if (v != -1) {
- change_mode(v);
+ // initialize to a value under SPEC
+ static freqman_index_t last_mode = WFM_MODULATION;
+ if (v > SPEC_MODULATION) {
+ if (last_mode == SPEC_MODULATION)
+ v = AM_MODULATION;
+ else
+ v = SPEC_MODULATION;
+ field_mode.set_selected_index(v);
}
+ last_mode = v;
+ change_mode(v);
};
// for some motive, audio output gets stopped.
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
@@ -1400,8 +1404,8 @@ void ReconView::start_repeat() {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
- p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
- p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
+ p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
+ p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
diff --git a/firmware/application/apps/ui_recon.hpp b/firmware/application/apps/ui_recon.hpp
index fa310770f..e610b2438 100644
--- a/firmware/application/apps/ui_recon.hpp
+++ b/firmware/application/apps/ui_recon.hpp
@@ -31,7 +31,6 @@
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "ui_mictx.hpp"
-#include "ui_level.hpp"
#include "ui_looking_glass_app.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
@@ -238,15 +237,15 @@ class ReconView : public View {
Text file_name{
// show file used
- {0, 1 * 16, SCREEN_W, 16},
+ {0, 1 * 16, screen_width, 16},
};
Text desc_cycle{
- {0, 2 * 16, SCREEN_W, 16},
+ {0, 2 * 16, screen_width, 16},
};
RSSI rssi{
- {0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
+ {0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
};
ButtonWithEncoder text_cycle{
@@ -286,15 +285,15 @@ class ReconView : public View {
// Button can be RECON or SCANNER
Button button_scanner_mode{
- {SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
+ {screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
"RECON"};
Button button_loop_config{
- {SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
+ {screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
"[LOOP]"};
Button button_config{
- {SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
+ {screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
"CONFIG"};
ButtonWithEncoder button_manual_start{
@@ -392,7 +391,7 @@ class ReconView : public View {
""};
ProgressBar progressbar{
- {0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
+ {0 * 8, screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
diff --git a/firmware/application/apps/ui_recon_settings.cpp b/firmware/application/apps/ui_recon_settings.cpp
index 520e1b4a4..83a3853cf 100644
--- a/firmware/application/apps/ui_recon_settings.cpp
+++ b/firmware/application/apps/ui_recon_settings.cpp
@@ -82,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
};
button_choose_output_name.on_select = [this, &nav](Button&) {
- text_prompt(nav, _output_file, 28, [this](std::string& buffer) {
+ text_prompt(nav, _output_file, 28, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& buffer) {
_output_file = buffer;
button_choose_output_name.set_text(_output_file);
});
diff --git a/firmware/application/apps/ui_recon_settings.hpp b/firmware/application/apps/ui_recon_settings.hpp
index eb5d257ec..fe4393369 100644
--- a/firmware/application/apps/ui_recon_settings.hpp
+++ b/firmware/application/apps/ui_recon_settings.hpp
@@ -57,10 +57,6 @@
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
-// screen size helper
-#define SCREEN_W 240
-// #define SCREEN_H 320
-
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 7
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
- Rect view_rect{0, 3 * 8, SCREEN_W, 230};
+ Rect view_rect{0, 3 * 8, screen_width, 230};
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
ReconSetupViewMore viewMore{nav_, view_rect};
diff --git a/firmware/application/apps/ui_search.cpp b/firmware/application/apps/ui_search.cpp
index 73fb5737f..86e041ac1 100644
--- a/firmware/application/apps/ui_search.cpp
+++ b/firmware/application/apps/ui_search.cpp
@@ -56,6 +56,10 @@ SearchView::SearchView(
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
+ if (!gradient.load_file(default_gradient_file)) {
+ gradient.set_default();
+ }
+
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
@@ -290,7 +294,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
power = spectrum.db[bin - 128];
}
- add_spectrum_pixel(spectrum_rgb3_lut[power]);
+ add_spectrum_pixel(gradient.lut[power]);
mean_acc += power;
if (power > max_power) {
diff --git a/firmware/application/apps/ui_search.hpp b/firmware/application/apps/ui_search.hpp
index c7663e28a..6d7c3afd1 100644
--- a/firmware/application/apps/ui_search.hpp
+++ b/firmware/application/apps/ui_search.hpp
@@ -24,7 +24,7 @@
#include "receiver_model.hpp"
#include "recent_entries.hpp"
#include "radio_state.hpp"
-#include "spectrum_color_lut.hpp"
+#include "gradient.hpp"
#include "ui_receiver.hpp"
namespace ui {
@@ -88,6 +88,7 @@ class SearchView : public View {
private:
NavigationView& nav_;
+ Gradient gradient{};
RxRadioState radio_state_{
100'000'000 /* frequency */,
2500000 /* bandwidth */,
diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp
index 3af587dca..295e83478 100644
--- a/firmware/application/apps/ui_settings.cpp
+++ b/firmware/application/apps/ui_settings.cpp
@@ -53,6 +53,8 @@ namespace fs = std::filesystem;
#include "i2cdevmanager.hpp"
#include "i2cdev_max17055.hpp"
+#include "file_reader.hpp"
+
extern ui::SystemView* system_view_ptr;
namespace pmem = portapack::persistent_memory;
@@ -324,6 +326,7 @@ SetUIView::SetUIView(NavigationView& nav) {
&toggle_bias_tee,
&toggle_clock,
&toggle_mute,
+ &toggle_fake_brightness,
&toggle_sd_card,
&button_save,
&button_cancel});
@@ -361,6 +364,7 @@ SetUIView::SetUIView(NavigationView& nav) {
toggle_clock.set_value(!pmem::ui_hide_clock());
toggle_speaker.set_value(!pmem::ui_hide_speaker());
toggle_mute.set_value(!pmem::ui_hide_mute());
+ toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
@@ -389,6 +393,7 @@ SetUIView::SetUIView(NavigationView& nav) {
pmem::set_ui_hide_clock(!toggle_clock.value());
pmem::set_ui_hide_speaker(!toggle_speaker.value());
pmem::set_ui_hide_mute(!toggle_mute.value());
+ pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
@@ -669,32 +674,6 @@ void SetAudioView::focus() {
button_save.focus();
}
-/* SetQRCodeView *****************************************/
-
-SetQRCodeView::SetQRCodeView(NavigationView& nav) {
- add_children({
- &labels,
- &checkbox_bigger_qr,
- &button_save,
- &button_cancel,
- });
-
- checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
-
- button_save.on_select = [&nav, this](Button&) {
- pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
- nav.pop();
- };
-
- button_cancel.on_select = [&nav, this](Button&) {
- nav.pop();
- };
-}
-
-void SetQRCodeView::focus() {
- button_save.focus();
-}
-
/* SetEncoderDialView ************************************/
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
@@ -702,14 +681,34 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
&field_encoder_dial_sensitivity,
&field_encoder_rate_multiplier,
&button_save,
- &button_cancel});
+ &button_cancel,
+ &button_dial_sensitivity_plus,
+ &button_dial_sensitivity_minus,
+ &button_rate_multiplier_plus,
+ &button_rate_multiplier_minus,
+ &field_encoder_dial_direction});
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
+ field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
+
+ button_dial_sensitivity_plus.on_select = [this](Button&) {
+ field_encoder_dial_sensitivity.on_encoder(1);
+ };
+ button_dial_sensitivity_minus.on_select = [this](Button&) {
+ field_encoder_dial_sensitivity.on_encoder(-1);
+ };
+ button_rate_multiplier_plus.on_select = [this](Button&) {
+ field_encoder_rate_multiplier.on_encoder(1);
+ };
+ button_rate_multiplier_minus.on_select = [this](Button&) {
+ field_encoder_rate_multiplier.on_encoder(-1);
+ };
button_save.on_select = [&nav, this](Button&) {
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
+ pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
nav.pop();
};
@@ -722,6 +721,36 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
+/* SetButtonsView ************************************/
+
+SetButtonsView::SetButtonsView(NavigationView& nav) {
+ add_children({&labels,
+ &button_save,
+ &button_cancel,
+ &field_repeat_delay,
+ &field_repeat_speed,
+ &field_long_press_delay});
+
+ field_repeat_delay.set_by_value(pmem::ui_button_repeat_delay());
+ field_repeat_speed.set_by_value(pmem::ui_button_repeat_speed());
+ field_long_press_delay.set_by_value(pmem::ui_button_long_press_delay());
+
+ button_save.on_select = [&nav, this](Button&) {
+ pmem::set_ui_button_repeat_delay(field_repeat_delay.selected_index_value());
+ pmem::set_ui_button_repeat_speed(field_repeat_speed.selected_index_value());
+ pmem::set_ui_button_long_press_delay(field_long_press_delay.selected_index_value());
+ nav.pop();
+ };
+
+ button_cancel.on_select = [&nav, this](Button&) {
+ nav.pop();
+ };
+}
+
+void SetButtonsView::focus() {
+ button_save.focus();
+}
+
/* AppSettingsView ************************************/
AppSettingsView::AppSettingsView(
@@ -730,7 +759,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
- menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
+ menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(settings_dir);
@@ -777,21 +806,35 @@ void SetConfigModeView::focus() {
/* SetDisplayView ************************************/
SetDisplayView::SetDisplayView(NavigationView& nav) {
- add_children({&button_save,
+ add_children({&labels,
+ &field_fake_brightness,
+ &button_save,
&button_cancel,
- &checkbox_invert_switch});
+ &checkbox_ips_screen_switch,
+ &checkbox_brightness_switch});
- checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
+ field_fake_brightness.set_by_value(pmem::fake_brightness_level());
+ checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
+ checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
button_save.on_select = [&nav, this](Button&) {
- if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
- display.set_inverted(checkbox_invert_switch.value());
- pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
+ pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
+ pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
+ if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
+ pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
}
send_system_refresh();
nav.pop();
};
+ // only enable invert OR fake brightness
+ checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
+ if (v) checkbox_brightness_switch.set_value(false);
+ };
+ checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
+ if (v) checkbox_ips_screen_switch.set_value(false);
+ };
+
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
@@ -957,67 +1000,6 @@ void SetMenuColorView::focus() {
button_save.focus();
}
-/* SetAutoStartView ************************************/
-
-SetAutostartView::SetAutostartView(NavigationView& nav) {
- add_children({&labels,
- &button_save,
- &button_cancel,
- &button_reset,
- &options});
-
- button_save.on_select = [&nav, this](Button&) {
- autostart_app = "";
- if (selected != 0) {
- auto it = full_app_list.find(selected);
- if (it != full_app_list.end())
- autostart_app = it->second;
- }
- nav.pop();
- };
-
- button_cancel.on_select = [&nav, this](Button&) {
- nav.pop();
- };
-
- button_reset.on_select = [this](Button&) {
- selected = 0;
- options.set_selected_index(0);
- autostart_app = "";
- };
-
- // options
- i = 0;
- OptionsField::option_t o{"-none-", i};
- opts.emplace_back(o);
- for (auto& app : NavigationView::appList) {
- if (app.id == nullptr) continue;
- i++;
- o = {app.displayName, i};
- opts.emplace_back(o);
- full_app_list.emplace(i, app.id);
- if (autostart_app == app.id) selected = i;
- }
- ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
- if (app.appCallName == nullptr) return;
- i++;
- OptionsField::option_t o = {app.appFriendlyName, i};
- opts.emplace_back(o);
- full_app_list.emplace(i, app.appCallName);
- if (autostart_app == app.appCallName) selected = i;
- });
-
- options.set_options(opts);
- options.on_change = [this](size_t, OptionsField::value_t v) {
- selected = v;
- };
- options.set_selected_index(selected);
-}
-
-void SetAutostartView::focus() {
- options.focus();
-}
-
/* SetThemeView ************************************/
SetThemeView::SetThemeView(NavigationView& nav) {
@@ -1061,12 +1043,14 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
- &checkbox_overridebatt});
+ &checkbox_overridebatt,
+ &checkbox_battery_charge_hint});
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
button_save.on_select = [&nav, this](Button&) {
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
+ pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
send_system_refresh();
nav.pop();
@@ -1081,6 +1065,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
};
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
+ checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
@@ -1099,9 +1084,12 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav)
}
void SettingsMenuView::on_populate() {
- if (pmem::show_gui_return_icon()) {
+ const bool return_icon = pmem::show_gui_return_icon();
+
+ if (return_icon) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
+
add_items({
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push(); }},
@@ -1111,18 +1099,20 @@ void SettingsMenuView::on_populate() {
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push(); }},
+ {"Button Speed", ui::Color::dark_cyan(), &bitmap_icon_controls, [this]() { nav_.push(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push(); }},
- //{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push(); }},
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push(); }},
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push(); }},
- {"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push(); }},
});
+
if (battery::BatteryManagement::isDetected()) add_item({"Battery", ui::Color::dark_cyan(), &bitmap_icon_batt_icon, [this]() { nav_.push(); }});
+
+ add_external_items(nav_, app_location_t::SETTINGS, *this, return_icon ? 1 : 0);
}
} /* namespace ui */
diff --git a/firmware/application/apps/ui_settings.hpp b/firmware/application/apps/ui_settings.hpp
index 9d31429d3..dcab27d5e 100644
--- a/firmware/application/apps/ui_settings.hpp
+++ b/firmware/application/apps/ui_settings.hpp
@@ -363,8 +363,12 @@ class SetUIView : public View {
{19 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_batt_text};
- ImageToggle toggle_sd_card{
+ ImageToggle toggle_fake_brightness{
{21 * 8, 14 * 16 + 2, 16, 16},
+ &bitmap_icon_brightness};
+
+ ImageToggle toggle_sd_card{
+ {23 * 8, 14 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
@@ -541,35 +545,7 @@ class SetAudioView : public View {
};
};
-class SetQRCodeView : public View {
- public:
- SetQRCodeView(NavigationView& nav);
-
- void focus() override;
-
- std::string title() const override { return "QR Code"; };
-
- private:
- Labels labels{
- {{1 * 8, 1 * 16}, "Change the size of the QR", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 2 * 16}, "code shown in Radiosonde.", Theme::getInstance()->fg_light->foreground},
- };
-
- Checkbox checkbox_bigger_qr{
- {3 * 8, 4 * 16},
- 20,
- "Show large QR code"};
-
- Button button_save{
- {2 * 8, 16 * 16, 12 * 8, 32},
- "Save"};
-
- Button button_cancel{
- {16 * 8, 16 * 16, 12 * 8, 32},
- "Cancel",
- };
-};
-
+using portapack::persistent_memory::encoder_dial_direction;
using portapack::persistent_memory::encoder_dial_sensitivity;
using portapack::persistent_memory::encoder_rate_multiplier;
@@ -583,30 +559,95 @@ class SetEncoderDialView : public View {
private:
Labels labels{
- {{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 2 * 16}, "rotation position (number of", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 3 * 16}, "steps per full rotation):", Theme::getInstance()->fg_light->foreground},
- {{2 * 8, 5 * 16}, "Dial sensitivity:", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 9 * 16}, "rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 10 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
- {{3 * 8, 12 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 0 * 16}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
+ {{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
+
};
OptionsField field_encoder_dial_sensitivity{
- {20 * 8, 5 * 16},
+ {20 * 8, 3 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
NumberField field_encoder_rate_multiplier{
- {20 * 8, 12 * 16},
+ {20 * 8, 10 * 16},
2,
{1, 15},
1,
' '};
+ OptionsField field_encoder_dial_direction{
+ {18 * 8, 14 * 16},
+ 7,
+ {{"NORMAL", false},
+ {"REVERSE", true}}};
+
+ Button button_dial_sensitivity_plus{
+ {20 * 8, 2 * 16, 16, 16},
+ "+"};
+
+ Button button_dial_sensitivity_minus{
+ {20 * 8, 4 * 16, 16, 16},
+ "-"};
+
+ Button button_rate_multiplier_plus{
+ {20 * 8, 9 * 16, 16, 16},
+ "+"};
+
+ Button button_rate_multiplier_minus{
+ {20 * 8, 11 * 16, 16, 16},
+ "-"};
+
+ Button button_save{
+ {2 * 8, 16 * 16, 12 * 8, 32},
+ "Save"};
+
+ Button button_cancel{
+ {16 * 8, 16 * 16, 12 * 8, 32},
+ "Cancel",
+ };
+};
+
+class SetButtonsView : public View {
+ public:
+ SetButtonsView(NavigationView& nav);
+ void focus() override;
+ std::string title() const override { return "Button Speed"; };
+
+ private:
+ Labels labels{
+ {{1 * 8, 1 * 16}, "Adjusts response time when a", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 2 * 16}, "button is held down.", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 5 * 16}, "Repeat delay:", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 7 * 16}, "Repeat speed:", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 9 * 16}, "Long press delay:", Theme::getInstance()->fg_light->foreground},
+ };
+
+ OptionsField field_repeat_delay{
+ {20 * 8, 5 * 16},
+ 6,
+ {{"NORMAL", false},
+ {"FAST", true}}};
+
+ OptionsField field_repeat_speed{
+ {20 * 8, 7 * 16},
+ 6,
+ {{"NORMAL", false},
+ {"FAST", true}}};
+
+ OptionsField field_long_press_delay{
+ {20 * 8, 9 * 16},
+ 6,
+ {{"NORMAL", false},
+ {"FAST", true}}};
+
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -672,7 +713,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
- {0, 2 * 8, 240, 26 * 8},
+ {0, 2 * 8, screen_width, 26 * 8},
true};
};
@@ -705,6 +746,7 @@ class SetConfigModeView : public View {
"Cancel",
};
};
+using portapack::persistent_memory::fake_brightness_level_options;
class SetDisplayView : public View {
public:
@@ -715,10 +757,31 @@ class SetDisplayView : public View {
std::string title() const override { return "Display"; };
private:
- Checkbox checkbox_invert_switch{
- {1 * 8, 2 * 16},
+ Labels labels{
+ {{1 * 8, 1 * 16}, "Limits screen brightness", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
+
+ };
+
+ OptionsField field_fake_brightness{
+ {20 * 8, 8 * 16},
+ 6,
+ {{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
+ {"25%", fake_brightness_level_options::BRIGHTNESS_25},
+ {"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
+
+ Checkbox checkbox_brightness_switch{
+ {1 * 8, 5 * 16},
+ 16,
+ "Enable brightness adjust"};
+
+ Checkbox checkbox_ips_screen_switch{
+ {1 * 8, 12 * 16},
23,
- "Invert colors (For IPS)"};
+ "IPS Screen"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -864,47 +927,6 @@ class SetMenuColorView : public View {
};
};
-class SetAutostartView : public View {
- public:
- SetAutostartView(NavigationView& nav);
-
- void focus() override;
-
- std::string title() const override { return "Autostart"; };
-
- private:
- int32_t i = 0;
- std::string autostart_app{""};
- OptionsField::options_t opts{};
- std::map full_app_list{}; // looking table
- int32_t selected = 0;
- SettingsStore nav_setting{
- "nav"sv,
- {{"autostart_app"sv, &autostart_app}}};
- Labels labels{
- {{1 * 8, 1 * 16}, "Select app to start on boot", Theme::getInstance()->fg_light->foreground},
- {{2 * 8, 2 * 16}, "(an SD Card is required)", Theme::getInstance()->fg_light->foreground}};
-
- Button button_save{
- {2 * 8, 16 * 16, 12 * 8, 32},
- "Save"};
-
- OptionsField options{
- {0 * 8, 4 * 16},
- screen_width / 8,
- {},
- true};
-
- Button button_cancel{
- {16 * 8, 16 * 16, 12 * 8, 32},
- "Cancel",
- };
-
- Button button_reset{
- {2 * 8, 6 * 16, screen_width - 4 * 8, 32},
- "Reset"};
-};
-
class SetThemeView : public View {
public:
SetThemeView(NavigationView& nav);
@@ -925,13 +947,14 @@ class SetThemeView : public View {
OptionsField options{
{0 * 8, 4 * 16},
- screen_width / 8,
+ (size_t)(screen_width / 8),
{
{"Default - Grey", 0},
{"Yellow", 1},
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
+ {"Dark", 5},
},
true};
@@ -958,9 +981,13 @@ class SetBatteryView : public View {
int32_t selected = 0;
Labels labels{
{{1 * 8, 1 * 16}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground}};
- Labels labels2{
- {{1 * 8, 6 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
+ {{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
+ /**/
+ {{1 * 8, 6 * 16}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 7 * 16}, "when you charge", Theme::getInstance()->fg_light->foreground}};
+
+ Labels labels2{{{1 * 8, 11 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
+
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -970,13 +997,18 @@ class SetBatteryView : public View {
23,
"Override"};
+ Checkbox checkbox_battery_charge_hint{
+ {2 * 8, 9 * 16},
+ 23,
+ "Charge hint"};
+
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
Button button_reset{
- {2 * 8, 8 * 16, 12 * 8, 32},
+ {2 * 8, 13 * 16, 12 * 8, 32},
"Reset",
};
};
@@ -988,6 +1020,7 @@ class SettingsMenuView : public BtnGridView {
private:
NavigationView& nav_;
+
void on_populate() override;
};
diff --git a/firmware/application/apps/ui_sigfrx.cpp b/firmware/application/apps/ui_sigfrx.cpp
index e04933060..516e898fc 100644
--- a/firmware/application/apps/ui_sigfrx.cpp
+++ b/firmware/application/apps/ui_sigfrx.cpp
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
- portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
+ portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
}
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
- portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
+ portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
uint8_t xmax = 0, imax = 0;
size_t i;
diff --git a/firmware/application/apps/ui_siggen.cpp b/firmware/application/apps/ui_siggen.cpp
index e34e57d3a..032dbd56c 100644
--- a/firmware/application/apps/ui_siggen.cpp
+++ b/firmware/application/apps/ui_siggen.cpp
@@ -44,9 +44,9 @@ SigGenView::~SigGenView() {
void SigGenView::update_config() {
if (checkbox_stop.value())
- baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), field_stop.value());
+ baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), field_stop.value());
else
- baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), 0);
+ baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), 0);
}
void SigGenView::update_tone() {
@@ -78,6 +78,7 @@ SigGenView::SigGenView(
baseband::run_image(portapack::spi_flash::image_tag_siggen);
add_children({&labels,
+ &options_mod,
&options_shape,
&text_shape,
&symfield_tone,
@@ -87,22 +88,48 @@ SigGenView::SigGenView(
&field_stop,
&tx_view});
- symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
+ symfield_tone.hidden(true); // At first launch , by default we are in CW: Shape ignored, we are not using Tone modulation.
+ options_shape.hidden(true);
+ text_shape.hidden(true);
symfield_tone.set_value(1000); // Default: 1000 Hz
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
text_shape.set(shape_strings[v]);
if (auto_update)
update_config();
- if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
- symfield_tone.hidden(1);
+
+ if (v == 5) { // In Shape Pseudo Random Noise we are not using Tone modulation freq.
+ symfield_tone.hidden(true);
} else {
- symfield_tone.hidden(0);
+ symfield_tone.hidden(false);
}
+
set_dirty();
};
options_shape.set_selected_index(0);
text_shape.set(shape_strings[0]);
+ options_mod.on_change = [this](size_t, OptionsField::value_t v) {
+ if (auto_update)
+ update_config();
+
+ if (v == 0) { // In Modulation Options CW we are not using Tone modulation freq.
+ symfield_tone.hidden(true);
+ } else {
+ symfield_tone.hidden(false);
+ }
+
+ if ((v == 0) || (v == 2) || (v == 3) || (v == 7)) { // In Modulation Options CW, QPSK, BPSK, Pulsed CW we are not using Shapes.
+ options_shape.hidden(true);
+ text_shape.hidden(true);
+ } else {
+ options_shape.hidden(false);
+ text_shape.hidden(false);
+ }
+
+ set_dirty();
+ };
+ options_mod.set_selected_index(0);
+
field_stop.set_value(1);
symfield_tone.set_value(1000); // Default: 1000 Hz
@@ -127,6 +154,13 @@ SigGenView::SigGenView(
};
};
+ tx_view.on_bandwidth_changed = [this]() {
+ // we don't protect here with auto_update because other field of tx_view obj isn't protected too
+ // to remains the design logic same
+
+ update_config();
+ };
+
tx_view.on_start = [this]() {
start_tx();
tx_view.set_transmitting(true);
@@ -138,4 +172,4 @@ SigGenView::SigGenView(
};
}
-} /* namespace ui */
+} /* namespace ui */
\ No newline at end of file
diff --git a/firmware/application/apps/ui_siggen.hpp b/firmware/application/apps/ui_siggen.hpp
index efd7dceaf..bbeeba28b 100644
--- a/firmware/application/apps/ui_siggen.hpp
+++ b/firmware/application/apps/ui_siggen.hpp
@@ -58,68 +58,74 @@ class SigGenView : public View {
app_settings::SettingsManager settings_{
"tx_siggen", app_settings::Mode::TX};
- const std::string shape_strings[9] = {
- "CW (No mod.) ",
- "Sine mod. FM",
- "Triangle mod.FM", // max 15 character text space.
- "Saw up mod. FM",
- "Saw down mod.FM",
- "Square mod. FM",
- "Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
- "BPSK 0,1,0,1...",
- "QPSK 00-01-10.."};
+ const std::string shape_strings[6] = {// max 15 character text space.
+ "Sine",
+ "Triangle",
+ "Saw up",
+ "Saw down",
+ "Square",
+ "Pseudo Noise"};
bool auto_update{false};
Labels labels{
- {{3 * 8, 4 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
- {{6 * 8, 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
- {{22 * 8, 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground},
- {{8 * 8, 20 * 8}, "Modulation: FM", Theme::getInstance()->fg_light->foreground}};
+ {{3 * 8, 2 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
+ {{3 * 8, 3 * 8 + 8 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
+ {{6 * 8, 2 * 8 + 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
+ {{22 * 8, 2 * 8 + 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground}};
ImageOptionsField options_shape{
- {10 * 8, 4, 32, 32},
+ {10 * 8, 3 * 8 + 8, 32, 32},
Theme::getInstance()->bg_darkest->foreground,
Theme::getInstance()->bg_darkest->background,
- {{&bitmap_sig_cw, 0},
- {&bitmap_sig_sine, 1},
- {&bitmap_sig_tri, 2},
- {&bitmap_sig_saw_up, 3},
- {&bitmap_sig_saw_down, 4},
- {&bitmap_sig_square, 5},
- {&bitmap_sig_noise, 6},
- {&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
- {&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
+ {{&bitmap_sig_sine, 0},
+ {&bitmap_sig_tri, 1},
+ {&bitmap_sig_saw_up, 2},
+ {&bitmap_sig_saw_down, 3},
+ {&bitmap_sig_square, 4},
+ {&bitmap_sig_noise, 5}}};
Text text_shape{
- {15 * 8, 4 + 10, 15 * 8, 16},
+ {15 * 8, 3 * 8 + 8 + 10, 15 * 8, 16},
""};
SymField symfield_tone{
- {12 * 8, 7 * 8},
+ {12 * 8, 2 * 8 + 7 * 8},
5};
Button button_update{
- {5 * 8, 10 * 8, 8 * 8, 3 * 8},
+ {5 * 8, 2 * 8 + 10 * 8, 8 * 8, 3 * 8},
"Update"};
Checkbox checkbox_auto{
- {15 * 8, 10 * 8},
+ {15 * 8, 2 * 8 + 10 * 8},
4,
"Auto"};
Checkbox checkbox_stop{
- {5 * 8, 15 * 8},
+ {5 * 8, 2 * 8 + 15 * 8},
10,
"Stop after"};
NumberField field_stop{
- {20 * 8, 15 * 8 + 4},
+ {20 * 8, 2 * 8 + 15 * 8 + 4},
2,
{1, 99},
1,
' '};
+ OptionsField options_mod{
+ {15 * 8, 2 * 8},
+ 12,
+ {{"CW (No mod.)", 0},
+ {"FM", 1},
+ {"BPSK", 2},
+ {"QPSK", 3},
+ {"DSB", 4},
+ {"AM 100% dep.", 5},
+ {"AM 50% depth", 6},
+ {"Pulse CW 25%", 7}}};
+
TransmitterView tx_view{
16 * 16,
10000,
diff --git a/firmware/application/apps/ui_sonde.hpp b/firmware/application/apps/ui_sonde.hpp
index 886ee44e4..2f1a4c0aa 100644
--- a/firmware/application/apps/ui_sonde.hpp
+++ b/firmware/application/apps/ui_sonde.hpp
@@ -120,7 +120,7 @@ class SondeView : public View {
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
Checkbox check_log{
{22 * 8, 8 * 16},
diff --git a/firmware/application/apps/ui_ss_viewer.cpp b/firmware/application/apps/ui_ss_viewer.cpp
index a0741041f..67ead2955 100644
--- a/firmware/application/apps/ui_ss_viewer.cpp
+++ b/firmware/application/apps/ui_ss_viewer.cpp
@@ -58,7 +58,7 @@ void ScreenshotViewer::paint(Painter& painter) {
}
// Screenshots from PNGWriter are all this size.
- if (file.size() != 232383) {
+ if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
show_invalid();
return;
}
@@ -66,7 +66,7 @@ void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
- std::array pixel_data;
+ std::vector pixel_data(screen_width);
// Seek past all the headers.
file.seek(43);
diff --git a/firmware/application/apps/ui_subghzd.cpp b/firmware/application/apps/ui_subghzd.cpp
index fa878252c..6a36075cb 100644
--- a/firmware/application/apps/ui_subghzd.cpp
+++ b/firmware/application/apps/ui_subghzd.cpp
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
+ &channel,
&field_rf_amp,
&field_lna,
&field_vga,
diff --git a/firmware/application/apps/ui_subghzd.hpp b/firmware/application/apps/ui_subghzd.hpp
index afdf79e20..232357db6 100644
--- a/firmware/application/apps/ui_subghzd.hpp
+++ b/firmware/application/apps/ui_subghzd.hpp
@@ -129,6 +129,9 @@ class SubGhzDView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
+ Channel channel{
+ {21 * 8, 5, 6 * 8, 4},
+ };
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
@@ -192,7 +195,7 @@ class SubGhzDRecentEntryDetailView : public View {
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
- {0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
+ {0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
diff --git a/firmware/application/apps/ui_test.cpp b/firmware/application/apps/ui_test.cpp
index 1029ac866..348577af1 100644
--- a/firmware/application/apps/ui_test.cpp
+++ b/firmware/application/apps/ui_test.cpp
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
cur_x++;
- if (cur_x >= 240) {
- display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
+ if (cur_x >= screen_width) {
+ display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
cur_x = 0;
}
diff --git a/firmware/application/apps/ui_test.hpp b/firmware/application/apps/ui_test.hpp
index c39f60773..48180ab25 100644
--- a/firmware/application/apps/ui_test.hpp
+++ b/firmware/application/apps/ui_test.hpp
@@ -95,10 +95,10 @@ class TestView : public View {
};
Text text_debug_a{
- {0 * 8, 4 * 16, 30 * 8, 16},
+ {0 * 8, 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
- {0 * 8, 5 * 16, 30 * 8, 16},
+ {0 * 8, 5 * 16, screen_width, 16},
"..."};
Button button_cal{
diff --git a/firmware/application/apps/ui_text_editor.cpp b/firmware/application/apps/ui_text_editor.cpp
index cb57704b2..ac05f5bf5 100644
--- a/firmware/application/apps/ui_text_editor.cpp
+++ b/firmware/application/apps/ui_text_editor.cpp
@@ -26,6 +26,7 @@
#include "log_file.hpp"
#include "string_format.hpp"
+#include "irq_controls.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
paint_cursor(painter);
}
+void TextViewer::enable_long_press() {
+ // Enable long press on "Select".
+ SwitchesState config;
+ config[toUType(Switch::Sel)] = true;
+ set_switches_long_press_config(config);
+}
+
+void TextViewer::on_focus() {
+ enable_long_press();
+}
+
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
- if (key == KeyEvent::Left)
- delta_col = -1;
- else if (key == KeyEvent::Right)
- delta_col = 1;
- else if (key == KeyEvent::Up)
- delta_line = -1;
- else if (key == KeyEvent::Down)
- delta_line = 1;
- else if (key == KeyEvent::Select && on_select) {
- on_select();
- return true;
+ if (key == KeyEvent::Select) {
+ // Toggle 'digit' mode with long-press.
+ if (digit_mode_ || key_is_long_pressed(key)) {
+ digit_mode_ = !digit_mode_;
+ set_dirty();
+ return true;
+ }
+ }
+
+ if (digit_mode_) {
+ switch (key) {
+ case KeyEvent::Left:
+ delta_col = -1;
+ break;
+ case KeyEvent::Right:
+ delta_col = 1;
+ break;
+ case KeyEvent::Up:
+ set_value(value_ == 0x0F ? 0x00 : value_ + 1);
+ break;
+ case KeyEvent::Down:
+ set_value(value_ == 0x00 ? 0x0F : value_ - 1);
+ break;
+ default:
+ return false;
+ }
+
+ if (delta_col == 0 && delta_line == 0) {
+ return true;
+ }
+ } else {
+ switch (key) {
+ case KeyEvent::Left:
+ delta_col = -1;
+ break;
+ case KeyEvent::Right:
+ delta_col = 1;
+ break;
+ case KeyEvent::Up:
+ delta_line = -1;
+ break;
+ case KeyEvent::Down:
+ delta_line = 1;
+ break;
+ case KeyEvent::Select: {
+ if (on_select) {
+ on_select();
+ return true;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
}
// Always allow cursor direction to be updated.
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
- if (cursor_.dir == ScrollDirection::Horizontal)
- updated = apply_scrolling_constraints(0, delta);
- else {
- delta *= 16;
- updated = apply_scrolling_constraints(delta, 0);
+ if (digit_mode_) {
+ if (delta > 0) {
+ set_value(value_ == 0x0F ? 0x00 : value_ + 1);
+ } else if (delta < 0) {
+ set_value(value_ == 0x00 ? 0x0F : value_ - 1);
+ }
+
+ updated = true;
+ } else {
+ if (cursor_.dir == ScrollDirection::Horizontal) {
+ updated = apply_scrolling_constraints(0, delta);
+ } else {
+ delta *= 16;
+ updated = apply_scrolling_constraints(delta, 0);
+ }
+
+ if (updated) {
+ redraw();
+ }
}
- if (updated)
- redraw();
-
return updated;
}
+void TextViewer::set_value(uint8_t new_value) {
+ if (new_value != value_) {
+ value_ = new_value;
+
+ if (on_change) {
+ on_change(value_);
+ }
+
+ set_dirty();
+ }
+}
+
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
paint_state_.redraw_text = redraw_text;
paint_state_.redraw_marked = redraw_marked;
@@ -617,6 +695,7 @@ void TextEditorView::show_edit_line() {
edit_line_buffer_,
viewer.col(),
max_edit_length,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
auto range = file_->line_range(viewer.line());
if (!range)
diff --git a/firmware/application/apps/ui_text_editor.hpp b/firmware/application/apps/ui_text_editor.hpp
index 18fdeb654..d40fcaeef 100644
--- a/firmware/application/apps/ui_text_editor.hpp
+++ b/firmware/application/apps/ui_text_editor.hpp
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
std::function on_select{};
std::function on_cursor_moved{};
+ std::function on_change{};
void paint(Painter& painter) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
+ void set_value(uint8_t new_value);
+ void on_focus() override;
void redraw(bool redraw_text = false, bool redraw_marked = false);
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
void cursor_set(uint16_t line, uint16_t col);
void cursor_mark_selected();
void cursor_clear_marked();
+ void enable_long_press();
typedef std::pair LineColPair;
std::vector lineColPair{};
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
int8_t char_height{};
uint8_t max_line{};
uint8_t max_col{};
+ bool digit_mode_{false};
+ uint8_t value_{0};
+ bool allow_digit_mode_{true};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
diff --git a/firmware/application/apps/ui_weatherstation.cpp b/firmware/application/apps/ui_weatherstation.cpp
index 2eae1607f..0c334731e 100644
--- a/firmware/application/apps/ui_weatherstation.cpp
+++ b/firmware/application/apps/ui_weatherstation.cpp
@@ -107,6 +107,7 @@ void WeatherView::focus() {
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
+ &channel,
&field_rf_amp,
&field_lna,
&field_vga,
diff --git a/firmware/application/apps/ui_weatherstation.hpp b/firmware/application/apps/ui_weatherstation.hpp
index 6c265e517..d12f91029 100644
--- a/firmware/application/apps/ui_weatherstation.hpp
+++ b/firmware/application/apps/ui_weatherstation.hpp
@@ -147,9 +147,12 @@ class WeatherView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
+ Channel channel{
+ {21 * 8, 5, 6 * 8, 4},
+ };
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp
index e288d9d66..2655f37f9 100644
--- a/firmware/application/baseband_api.cpp
+++ b/firmware/application/baseband_api.cpp
@@ -66,12 +66,13 @@ static void send_message(const Message* const message) {
void AMConfig::apply() const {
const AMConfigureMessage message{
- taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 5 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW)
- taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 5 x AM mod. types.
- decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k all rest AM modes)
- channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW)
- modulation, // var parameter .
- audio_12k_hpf_300hz_config};
+ taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, AMFM-WFAX)
+ decim_1, // var decim_1 FIR taps filter , variable values , to handle two spectrum decim factor 1 and 2 (zoom) and more APT LPF filtered .
+ decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes)
+ channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,AMFM-WFAX)
+ modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
+ audio_12k_iir_filter_config, // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf)
+ spectrum_decimation_factor}; // var parameter , waterfall no zoom : 1 ,for zoom x 2 : 2
send_message(&message);
audio::set_rate(audio::Rate::Hz_12000);
}
@@ -92,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
void WFMConfig::apply() const {
const WFMConfigureMessage message{
- decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
- decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
- taps_64_lp_156_198,
+ decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
+ decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
+ taps_64_lp_156_198, // Fixed channel audio filter 15khz
75000,
audio_48k_hpf_30hz_config,
audio_48k_deemph_2122_6_config};
@@ -102,6 +103,18 @@ void WFMConfig::apply() const {
audio::set_rate(audio::Rate::Hz_48000);
}
+void WFMAMConfig::apply() const {
+ const WFMAMConfigureMessage message{
+ decim_0, // Fixed 24 taps array : taps_16k0_decim_0
+ decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
+ taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
+ 17000, // NOAA satellite tx , FM deviation = +-17Khz.
+ apt_audio_12k_notch_2k4_config,
+ apt_audio_12k_lpf_2000hz_config};
+ send_message(&message);
+ audio::set_rate(audio::Rate::Hz_12000);
+}
+
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
@@ -173,13 +186,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
send_message(&message);
}
-void set_fsk(const size_t deviation) {
+void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
const FSKRxConfigureMessage message{
- taps_200k_decim_0,
- taps_16k0_decim_1,
- taps_11k0_channel,
- 2,
- deviation};
+ samplesPerSymbol,
+ syncWord,
+ syncWordLength,
+ preamble,
+ preambleLength,
+ numDataBytes};
send_message(&message);
}
@@ -304,6 +318,16 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger) {
send_message(&message);
}
+void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
+ const WeFaxRxConfigureMessage message{lpm, ioc};
+ send_message(&message);
+}
+
+void set_noaaapt_config() {
+ const NoaaAptRxConfigureMessage message{};
+ send_message(&message);
+}
+
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp
index a594fc273..84d5f292a 100644
--- a/firmware/application/baseband_api.hpp
+++ b/firmware/application/baseband_api.hpp
@@ -36,9 +36,12 @@
namespace baseband {
struct AMConfig {
- const fir_taps_real<32> decim_2; // added to handle two types decim_2 9k, 6k
+ const fir_taps_real<32> decim_1; // added to handle two var LPF in AMFM to avoid aliasing when spectrum zoom factor 2.
+ const fir_taps_real<32> decim_2; // added to handle two var types decim_2 9k, 6k
const fir_taps_complex<64> channel;
const AMConfigureMessage::Modulation modulation;
+ const iir_biquad_config_t audio_12k_iir_filter_config; // added to handle two var IIR filter types : 300 hpf(as before) , 1500Hz lpf for Wefax.
+ const size_t spectrum_decimation_factor; // used to handle LCD AM waterfall zoom x1 / zoom x2.
void apply() const;
};
@@ -53,12 +56,20 @@ struct NBFMConfig {
};
struct WFMConfig {
- const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
+ const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
const fir_taps_real<16> decim_1;
void apply() const;
};
+struct WFMAMConfig {
+ const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
+ const fir_taps_real<32> decim_1;
+ const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
+
+ void apply() const;
+};
+
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
@@ -69,7 +80,7 @@ void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
-void set_fsk(const size_t deviation);
+void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_aprs(const uint32_t baudrate);
void set_btlerx(uint8_t channel_number);
@@ -89,6 +100,8 @@ void set_siggen_tone(const uint32_t tone);
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
+void set_wefax_config(uint8_t lpm, uint8_t ioc);
+void set_noaaapt_config();
void request_roger_beep();
void request_rssi_beep();
diff --git a/firmware/application/binder.hpp b/firmware/application/binder.hpp
index 1b4c01ec0..40e877829 100644
--- a/firmware/application/binder.hpp
+++ b/firmware/application/binder.hpp
@@ -25,6 +25,7 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_widget.hpp"
+#include "ui_textentry.hpp"
namespace ui {
@@ -105,7 +106,7 @@ void bind(TextField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
// Capture a new string and make the lambda mutable so it can be modified.
field.on_select = [&nav, buf = std::string{}](TextField& tf) mutable {
buf = tf.get_text();
- text_prompt(nav, buf, /*max_length*/ 255,
+ text_prompt(nav, buf, /*max_length*/ 255, ENTER_KEYBOARD_MODE_ALPHA,
[&tf](std::string& str) {
tf.set_text(str);
});
diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp
index 704d727c6..f3e1767d2 100644
--- a/firmware/application/bitmap.hpp
+++ b/firmware/application/bitmap.hpp
@@ -29,543 +29,429 @@
namespace ui {
-static constexpr uint8_t bitmap_icon_receivers_data[] = {
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x98,
- 0x19,
- 0xB0,
- 0x0D,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0x83,
- 0xC1,
- 0x03,
- 0xC0,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
-};
-static constexpr Bitmap bitmap_icon_receivers{
- {16, 16},
- bitmap_icon_receivers_data};
-
-static constexpr uint8_t bitmap_icon_utilities_data[] = {
- 0x30,
- 0x24,
- 0x78,
- 0x66,
- 0x78,
- 0x66,
- 0x78,
- 0x7E,
- 0x78,
- 0x3C,
- 0x78,
- 0x18,
- 0x78,
- 0x18,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x3C,
- 0x30,
- 0x18,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_utilities{
- {16, 16},
- bitmap_icon_utilities_data};
-
-static constexpr uint8_t bitmap_stripes_data[] = {
- 0xFF,
- 0x03,
- 0xC0,
- 0xFF,
- 0x01,
- 0xE0,
- 0xFF,
- 0x00,
- 0xF0,
- 0x7F,
- 0x00,
- 0xF8,
- 0x3F,
- 0x00,
- 0xFC,
- 0x1F,
- 0x00,
- 0xFE,
- 0x0F,
- 0x00,
- 0xFF,
- 0x07,
- 0x80,
- 0xFF,
-};
-static constexpr Bitmap bitmap_stripes{
- {24, 8},
- bitmap_stripes_data};
-
-static constexpr uint8_t bitmap_icon_sstv_data[] = {
+static constexpr uint8_t bitmap_arrow_left_data[] = {
+ 0x00,
0x00,
0x00,
0x00,
0x00,
- 0xFE,
- 0x7F,
- 0x03,
- 0xC0,
- 0x53,
- 0xD5,
- 0xAB,
- 0xCA,
- 0x53,
- 0xD5,
- 0xAB,
- 0xCA,
- 0x53,
- 0xD5,
- 0xAB,
- 0xCA,
- 0x53,
- 0xD5,
- 0x03,
- 0xC0,
- 0xFF,
- 0xFF,
- 0xFB,
- 0xD7,
- 0xFE,
- 0x7F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_sstv{
- {16, 16},
- bitmap_icon_sstv_data};
-
-static constexpr uint8_t bitmap_icon_morse_data[] = {
- 0x00,
- 0x00,
- 0xFE,
- 0x7F,
- 0xFF,
- 0xFF,
- 0xBB,
- 0xD0,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0x0B,
- 0xE1,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xEB,
- 0xD0,
- 0xFF,
- 0xFF,
- 0xFE,
- 0x7F,
- 0x70,
- 0x00,
- 0x30,
- 0x00,
- 0x10,
0x00,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_morse{
- {16, 16},
- bitmap_icon_morse_data};
-
-static constexpr uint8_t bitmap_icon_file_wav_data[] = {
- 0xFC,
- 0x03,
- 0x04,
- 0x06,
- 0x04,
- 0x0E,
- 0x04,
- 0x1E,
- 0x04,
- 0x3E,
- 0x84,
0x20,
- 0xC4,
- 0x22,
- 0xF4,
- 0x20,
- 0xF4,
- 0x2E,
- 0xF4,
- 0x20,
- 0xC4,
- 0x22,
- 0x84,
- 0x24,
- 0x04,
- 0x28,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0xFC,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_file_wav{
- {16, 16},
- bitmap_icon_file_wav_data};
-
-static constexpr uint8_t bitmap_icon_tetra_data[] = {
- 0xE0,
- 0x0F,
- 0x18,
+ 0x00,
+ 0x30,
+ 0x00,
0x38,
- 0xE4,
- 0x67,
- 0x7E,
- 0xCE,
- 0xC7,
- 0xCC,
+ 0x00,
+ 0xFC,
+ 0x7F,
+ 0xFE,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0x38,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x20,
0x00,
0x00,
- 0xFF,
- 0x4F,
- 0xBA,
- 0xB2,
- 0x9A,
- 0xEE,
- 0xBA,
- 0xB2,
0x00,
0x00,
- 0x3B,
- 0xE3,
- 0x73,
- 0x7E,
- 0xC6,
- 0x27,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_arrow_left{
+ {16, 16},
+ bitmap_arrow_left_data};
+
+static constexpr uint8_t bitmap_arrow_right_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x0C,
+ 0x00,
0x1C,
- 0x18,
- 0xF0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_tetra{
- {16, 16},
- bitmap_icon_tetra_data};
-
-static constexpr uint8_t bitmap_icon_debug_data[] = {
0xFE,
- 0x03,
- 0x02,
- 0x07,
- 0x2A,
- 0x0D,
- 0x52,
- 0x0F,
- 0x2A,
- 0x08,
- 0x52,
- 0x09,
- 0xAA,
- 0x0A,
- 0x52,
- 0x09,
- 0xAA,
- 0x0A,
- 0x52,
- 0x01,
- 0xAA,
- 0x12,
- 0x02,
- 0x08,
- 0x02,
- 0xFC,
- 0x02,
- 0x08,
- 0xFE,
- 0x13,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_debug{
- {16, 16},
- bitmap_icon_debug_data};
-
-static constexpr uint8_t bitmap_icon_downconvert_data[] = {
- 0x00,
- 0x00,
- 0x77,
- 0x77,
- 0x51,
- 0x51,
- 0x33,
- 0x53,
- 0x51,
- 0x51,
- 0x51,
- 0x77,
- 0x00,
- 0x80,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0xF8,
- 0x1F,
- 0xF0,
- 0x0F,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
-};
-static constexpr Bitmap bitmap_icon_downconvert{
- {16, 16},
- bitmap_icon_downconvert_data};
-
-static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
- 0x20,
- 0x10,
- 0x30,
- 0x20,
- 0x38,
- 0x44,
- 0x3E,
- 0x48,
- 0x3E,
- 0x91,
- 0x3E,
- 0x92,
- 0x38,
- 0x92,
- 0x30,
- 0x92,
- 0x20,
- 0x92,
- 0x00,
- 0x92,
- 0x30,
- 0x91,
- 0x48,
- 0x48,
- 0x84,
- 0x44,
- 0x84,
- 0x20,
- 0x86,
- 0x11,
- 0x86,
- 0x01,
-};
-static constexpr Bitmap bitmap_icon_speaker_and_headphones{
- {16, 16},
- bitmap_icon_speaker_and_headphones_data};
-
-static constexpr uint8_t bitmap_more_data[] = {
- 0x10,
- 0x10,
- 0x10,
- 0x10,
- 0x54,
- 0x38,
- 0x10,
- 0x00,
-};
-static constexpr Bitmap bitmap_more{
- {8, 8},
- bitmap_more_data};
-
-static constexpr uint8_t bitmap_icon_nuoptix_data[] = {
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x40,
- 0x02,
- 0x40,
- 0x1A,
- 0x40,
- 0x1A,
- 0x20,
- 0x0C,
- 0x20,
- 0x0F,
- 0x20,
- 0x1E,
- 0x10,
- 0x0E,
- 0x10,
- 0x0B,
- 0x10,
- 0x0B,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xFC,
0x3F,
- 0xFC,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_nuoptix{
- {16, 16},
- bitmap_icon_nuoptix_data};
-
-static constexpr uint8_t bitmap_icon_search_data[] = {
- 0xF8,
- 0x01,
- 0xFC,
- 0x03,
- 0x0E,
- 0x07,
- 0x07,
- 0x0E,
- 0x03,
- 0x0C,
- 0x0B,
- 0x0C,
- 0x0B,
- 0x0C,
- 0x13,
- 0x0C,
- 0x07,
- 0x0E,
- 0x0E,
- 0x07,
- 0xFC,
- 0x1F,
- 0xF8,
- 0x3D,
- 0x00,
- 0x7C,
- 0x00,
- 0xF8,
- 0x00,
- 0xF0,
- 0x00,
- 0x60,
-};
-static constexpr Bitmap bitmap_icon_search{
- {16, 16},
- bitmap_icon_search_data};
-
-static constexpr uint8_t bitmap_icon_biast_on_data[] = {
- 0x00,
- 0x00,
0xFE,
0x7F,
+ 0xFE,
+ 0x3F,
0x00,
- 0x04,
- 0x00,
- 0x08,
- 0x20,
- 0x10,
- 0x20,
- 0x08,
- 0x30,
- 0x04,
- 0x30,
- 0x08,
- 0xF8,
- 0x10,
- 0x60,
- 0x08,
- 0x60,
- 0x04,
- 0x20,
- 0x08,
- 0x20,
- 0x10,
- 0x00,
- 0x08,
- 0x00,
- 0x04,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_biast_on{
- {16, 16},
- bitmap_icon_biast_on_data};
-
-static constexpr uint8_t bitmap_icon_cwgen_data[] = {
- 0x18,
- 0x00,
- 0x24,
- 0x00,
- 0x42,
- 0x00,
- 0x42,
- 0x00,
- 0x42,
- 0x00,
- 0x42,
- 0x00,
- 0x81,
- 0x00,
- 0xAB,
- 0x6A,
- 0x80,
- 0x40,
- 0x00,
- 0x21,
- 0x00,
- 0x21,
- 0x00,
- 0x21,
- 0x00,
- 0x21,
- 0x00,
- 0x12,
+ 0x1C,
0x00,
0x0C,
0x00,
+ 0x04,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
0x00,
};
-static constexpr Bitmap bitmap_icon_cwgen{
+static constexpr Bitmap bitmap_arrow_right{
{16, 16},
- bitmap_icon_cwgen_data};
+ bitmap_arrow_right_data};
+
+static constexpr uint8_t bitmap_bulb_ignore_data[] = {
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0x01,
+ 0x40,
+ 0x3C,
+ 0x02,
+ 0x20,
+ 0x7E,
+ 0x04,
+ 0x20,
+ 0xE7,
+ 0x04,
+ 0x10,
+ 0xC3,
+ 0x08,
+ 0x10,
+ 0xE3,
+ 0x08,
+ 0x10,
+ 0x70,
+ 0x08,
+ 0x10,
+ 0x38,
+ 0x08,
+ 0x10,
+ 0x18,
+ 0x08,
+ 0x20,
+ 0x18,
+ 0x04,
+ 0x20,
+ 0x00,
+ 0x04,
+ 0x40,
+ 0x18,
+ 0x02,
+ 0x80,
+ 0x18,
+ 0x01,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x00,
+ 0x3C,
+ 0x00,
+};
+static constexpr Bitmap bitmap_bulb_ignore{
+ {24, 24},
+ bitmap_bulb_ignore_data};
+
+static constexpr uint8_t bitmap_bulb_off_data[] = {
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0x01,
+ 0x40,
+ 0x00,
+ 0x02,
+ 0x20,
+ 0x00,
+ 0x04,
+ 0x20,
+ 0x00,
+ 0x04,
+ 0x10,
+ 0x00,
+ 0x08,
+ 0x10,
+ 0x42,
+ 0x08,
+ 0x10,
+ 0x42,
+ 0x08,
+ 0x10,
+ 0x24,
+ 0x08,
+ 0x10,
+ 0x24,
+ 0x08,
+ 0x20,
+ 0x24,
+ 0x04,
+ 0x20,
+ 0x2C,
+ 0x04,
+ 0x40,
+ 0x34,
+ 0x02,
+ 0x80,
+ 0x3C,
+ 0x01,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0xE3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x00,
+ 0x3C,
+ 0x00,
+};
+static constexpr Bitmap bitmap_bulb_off{
+ {24, 24},
+ bitmap_bulb_off_data};
+
+static constexpr uint8_t bitmap_bulb_on_data[] = {
+ 0x04,
+ 0x3C,
+ 0x20,
+ 0x08,
+ 0xFF,
+ 0x10,
+ 0x90,
+ 0xFF,
+ 0x09,
+ 0xC0,
+ 0xFF,
+ 0x03,
+ 0xE0,
+ 0xFF,
+ 0x07,
+ 0xE0,
+ 0xFF,
+ 0x07,
+ 0xF0,
+ 0xE7,
+ 0x0F,
+ 0xF0,
+ 0xBD,
+ 0x0F,
+ 0xF7,
+ 0xBD,
+ 0xEF,
+ 0xF0,
+ 0xDB,
+ 0x0F,
+ 0xF0,
+ 0xDB,
+ 0x0F,
+ 0xE0,
+ 0xDB,
+ 0x07,
+ 0xE0,
+ 0xCB,
+ 0x07,
+ 0xC0,
+ 0xD3,
+ 0x03,
+ 0x90,
+ 0xCB,
+ 0x09,
+ 0x08,
+ 0xFD,
+ 0x10,
+ 0x04,
+ 0xE3,
+ 0x20,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0x00,
+ 0xBD,
+ 0x00,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x00,
+ 0x3C,
+ 0x00,
+};
+static constexpr Bitmap bitmap_bulb_on{
+ {24, 24},
+ bitmap_bulb_on_data};
+
+static constexpr uint8_t bitmap_icon_add_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_add{
+ {16, 16},
+ bitmap_icon_add_data};
+
+static constexpr uint8_t bitmap_icon_adsb_data[] = {
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0xFE,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xF0,
+ 0x0F,
+ 0xF8,
+ 0x1F,
+};
+static constexpr Bitmap bitmap_icon_adsb{
+ {16, 16},
+ bitmap_icon_adsb_data};
+
+static constexpr uint8_t bitmap_icon_ais_data[] = {
+ 0x00,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x0D,
+ 0xE0,
+ 0x3D,
+ 0xF0,
+ 0x3D,
+ 0xF8,
+ 0x7D,
+ 0xFC,
+ 0x7D,
+ 0xFC,
+ 0x7D,
+ 0xFE,
+ 0x7D,
+ 0xFF,
+ 0x7D,
+ 0x00,
+ 0x00,
+ 0xF8,
+ 0x7F,
+ 0xF8,
+ 0x3F,
+ 0xF0,
+ 0x0F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_ais{
+ {16, 16},
+ bitmap_icon_ais_data};
static constexpr uint8_t bitmap_icon_aprs_data[] = {
0x00,
@@ -605,425 +491,81 @@ static constexpr Bitmap bitmap_icon_aprs{
{16, 16},
bitmap_icon_aprs_data};
-static constexpr uint8_t bitmap_icon_burger_data[] = {
+static constexpr uint8_t bitmap_icon_back_data[] = {
0x00,
0x00,
- 0xE0,
- 0x07,
- 0xF8,
- 0x1F,
- 0xFC,
+ 0x30,
+ 0x00,
+ 0x38,
+ 0x00,
+ 0x1C,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0xFF,
0x3F,
- 0xFE,
+ 0xFF,
0x7F,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
+ 0x0E,
+ 0xE0,
+ 0x1C,
+ 0xC0,
+ 0x38,
+ 0xC0,
+ 0x30,
+ 0xC0,
0x00,
+ 0xE0,
0x00,
- 0x55,
- 0x55,
- 0xAA,
- 0xAA,
- 0x55,
- 0x55,
- 0x00,
- 0x00,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFE,
0x7F,
0x00,
+ 0x3F,
+ 0x00,
+ 0x00,
+ 0x00,
0x00,
};
-static constexpr Bitmap bitmap_icon_burger{
+static constexpr Bitmap bitmap_icon_back{
{16, 16},
- bitmap_icon_burger_data};
+ bitmap_icon_back_data};
-static constexpr uint8_t bitmap_sig_tri_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x03,
- 0xC0,
- 0x00,
- 0x00,
- 0x03,
- 0xC0,
- 0x00,
- 0x80,
- 0x07,
- 0xE0,
- 0x01,
- 0x80,
- 0x07,
- 0xE0,
- 0x01,
- 0xC0,
- 0x0C,
- 0x30,
- 0x03,
- 0xC0,
- 0x0C,
- 0x30,
- 0x03,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x18,
- 0x60,
- 0x06,
- 0x18,
- 0x18,
- 0x60,
- 0x06,
- 0x18,
- 0x0E,
+static constexpr uint8_t bitmap_icon_batt_icon_data[] = {
0xC0,
0x03,
- 0x70,
- 0x0E,
0xC0,
0x03,
- 0x70,
- 0x06,
- 0x80,
- 0x01,
- 0x60,
- 0x06,
- 0x80,
- 0x01,
- 0x60,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_sig_tri{
- {32, 32},
- bitmap_sig_tri_data};
-
-static constexpr uint8_t bitmap_sig_sine_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x03,
- 0xC0,
- 0x00,
- 0x80,
- 0x07,
- 0xE0,
- 0x01,
- 0xC0,
+ 0xF0,
+ 0x0F,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0xF0,
0x0F,
0xF0,
- 0x03,
- 0xC0,
- 0x0C,
- 0x30,
- 0x03,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x60,
- 0x18,
- 0x18,
- 0x06,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x30,
- 0x30,
- 0x0C,
- 0x0C,
- 0x18,
- 0x60,
- 0x06,
- 0x18,
- 0x1E,
- 0xE0,
- 0x07,
- 0x78,
- 0x0E,
- 0xC0,
- 0x03,
- 0x70,
- 0x06,
- 0x80,
- 0x01,
- 0x60,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
0x00,
0x00,
};
-static constexpr Bitmap bitmap_sig_sine{
- {32, 32},
- bitmap_sig_sine_data};
-
-static constexpr uint8_t bitmap_icon_biast_off_data[] = {
- 0x00,
- 0x00,
- 0xFE,
- 0x7F,
- 0x00,
- 0x04,
- 0x00,
- 0x08,
- 0x00,
- 0x10,
- 0x00,
- 0x08,
- 0x88,
- 0x04,
- 0x50,
- 0x08,
- 0x20,
- 0x10,
- 0x50,
- 0x08,
- 0x88,
- 0x04,
- 0x00,
- 0x08,
- 0x00,
- 0x10,
- 0x00,
- 0x08,
- 0x00,
- 0x04,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_biast_off{
+static constexpr Bitmap bitmap_icon_batt_icon{
{16, 16},
- bitmap_icon_biast_off_data};
-
-static constexpr uint8_t bitmap_icon_previous_data[] = {
- 0x00,
- 0x00,
- 0xC0,
- 0x00,
- 0xE0,
- 0x00,
- 0x70,
- 0x00,
- 0x38,
- 0x00,
- 0x1C,
- 0x00,
- 0x0E,
- 0x00,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0x0E,
- 0x00,
- 0x1C,
- 0x00,
- 0x38,
- 0x00,
- 0x70,
- 0x00,
- 0xE0,
- 0x00,
- 0xC0,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_previous{
- {16, 16},
- bitmap_icon_previous_data};
-
-static constexpr uint8_t bitmap_icon_tools_antenna_data[] = {
- 0x38,
- 0x3E,
- 0x10,
- 0x22,
- 0x10,
- 0x26,
- 0x10,
- 0x22,
- 0x10,
- 0x2E,
- 0x10,
- 0x22,
- 0x10,
- 0x26,
- 0x10,
- 0x22,
- 0x38,
- 0x2E,
- 0x38,
- 0x22,
- 0x38,
- 0x26,
- 0x38,
- 0x22,
- 0x38,
- 0x2E,
- 0x38,
- 0x22,
- 0x38,
- 0x3E,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_tools_antenna{
- {16, 16},
- bitmap_icon_tools_antenna_data};
+ bitmap_icon_batt_icon_data};
static constexpr uint8_t bitmap_icon_batt_text_data[] = {
0x00,
@@ -1063,6 +605,1798 @@ static constexpr Bitmap bitmap_icon_batt_text{
{16, 16},
bitmap_icon_batt_text_data};
+static constexpr uint8_t bitmap_icon_bht_data[] = {
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x08,
+ 0x9C,
+ 0x07,
+ 0x0C,
+ 0x00,
+ 0x8E,
+ 0x0A,
+ 0x46,
+ 0x12,
+ 0x26,
+ 0x22,
+ 0x06,
+ 0x02,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_bht{
+ {16, 16},
+ bitmap_icon_bht_data};
+
+static constexpr uint8_t bitmap_icon_biast_off_data[] = {
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x08,
+ 0x00,
+ 0x10,
+ 0x00,
+ 0x08,
+ 0x88,
+ 0x04,
+ 0x50,
+ 0x08,
+ 0x20,
+ 0x10,
+ 0x50,
+ 0x08,
+ 0x88,
+ 0x04,
+ 0x00,
+ 0x08,
+ 0x00,
+ 0x10,
+ 0x00,
+ 0x08,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_biast_off{
+ {16, 16},
+ bitmap_icon_biast_off_data};
+
+static constexpr uint8_t bitmap_icon_biast_on_data[] = {
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x08,
+ 0x20,
+ 0x10,
+ 0x20,
+ 0x08,
+ 0x30,
+ 0x04,
+ 0x30,
+ 0x08,
+ 0xF8,
+ 0x10,
+ 0x60,
+ 0x08,
+ 0x60,
+ 0x04,
+ 0x20,
+ 0x08,
+ 0x20,
+ 0x10,
+ 0x00,
+ 0x08,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_biast_on{
+ {16, 16},
+ bitmap_icon_biast_on_data};
+
+static constexpr uint8_t bitmap_icon_breakout_data[] = {
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+};
+static constexpr Bitmap bitmap_icon_breakout{
+ {16, 16},
+ bitmap_icon_breakout_data};
+
+static constexpr uint8_t bitmap_icon_brightness_data[] = {
+ 0x00,
+ 0x00,
+ 0x80,
+ 0x01,
+ 0x84,
+ 0x21,
+ 0x08,
+ 0x10,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xF0,
+ 0x0F,
+ 0xF6,
+ 0x6F,
+ 0xF6,
+ 0x6F,
+ 0xF0,
+ 0x0F,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0x08,
+ 0x10,
+ 0x84,
+ 0x21,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_brightness{
+ {16, 16},
+ bitmap_icon_brightness_data};
+
+static constexpr uint8_t bitmap_icon_btle_data[] = {
+ 0xE0,
+ 0x03,
+ 0x30,
+ 0x07,
+ 0x38,
+ 0x0E,
+ 0x3C,
+ 0x1C,
+ 0x24,
+ 0x19,
+ 0x0C,
+ 0x13,
+ 0x1C,
+ 0x19,
+ 0x3C,
+ 0x1C,
+ 0x3C,
+ 0x1C,
+ 0x1C,
+ 0x19,
+ 0x0C,
+ 0x13,
+ 0x24,
+ 0x19,
+ 0x3C,
+ 0x1C,
+ 0x38,
+ 0x0E,
+ 0x30,
+ 0x07,
+ 0xE0,
+ 0x03,
+};
+static constexpr Bitmap bitmap_icon_btle{
+ {16, 16},
+ bitmap_icon_btle_data};
+
+static constexpr uint8_t bitmap_icon_burger_data[] = {
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0xFC,
+ 0x3F,
+ 0xFE,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x55,
+ 0x55,
+ 0xAA,
+ 0xAA,
+ 0x55,
+ 0x55,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_burger{
+ {16, 16},
+ bitmap_icon_burger_data};
+
+static constexpr uint8_t bitmap_icon_camera_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x07,
+ 0xF0,
+ 0x0F,
+ 0x3E,
+ 0x7C,
+ 0xDE,
+ 0x7B,
+ 0xEE,
+ 0x77,
+ 0xEE,
+ 0x77,
+ 0xEE,
+ 0x77,
+ 0xEE,
+ 0x77,
+ 0xDE,
+ 0x7B,
+ 0x3E,
+ 0x7C,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_camera{
+ {16, 16},
+ bitmap_icon_camera_data};
+
+static constexpr uint8_t bitmap_icon_capture_data[] = {
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0xFC,
+ 0x3F,
+ 0xFE,
+ 0x7F,
+ 0xFE,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFE,
+ 0x7F,
+ 0xFE,
+ 0x7F,
+ 0xFC,
+ 0x3F,
+ 0xF8,
+ 0x1F,
+ 0xE0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_capture{
+ {16, 16},
+ bitmap_icon_capture_data};
+
+static constexpr uint8_t bitmap_icon_clean_data[] = {
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x01,
+ 0x20,
+ 0x02,
+ 0xFC,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0x08,
+ 0x08,
+ 0xE8,
+ 0x08,
+ 0xA8,
+ 0x09,
+ 0xA8,
+ 0x0B,
+ 0x28,
+ 0x0A,
+ 0x28,
+ 0x0A,
+ 0x28,
+ 0x0A,
+ 0xE8,
+ 0x0B,
+ 0x08,
+ 0x08,
+ 0xF0,
+ 0x07,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_clean{
+ {16, 16},
+ bitmap_icon_clean_data};
+
+static constexpr uint8_t bitmap_icon_clk_ext_data[] = {
+ 0x00,
+ 0x00,
+ 0xDC,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x76,
+ 0x00,
+ 0x10,
+ 0x38,
+ 0x7C,
+ 0x10,
+ 0x10,
+ 0x10,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_clk_ext{
+ {8, 16},
+ bitmap_icon_clk_ext_data};
+
+static constexpr uint8_t bitmap_icon_clk_int_data[] = {
+ 0x00,
+ 0x00,
+ 0xDC,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x76,
+ 0x00,
+ 0x44,
+ 0x6C,
+ 0x38,
+ 0x38,
+ 0x6C,
+ 0x44,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_clk_int{
+ {8, 16},
+ bitmap_icon_clk_int_data};
+
+static constexpr uint8_t bitmap_icon_codetx_data[] = {
+ 0x00,
+ 0x00,
+ 0xF0,
+ 0x07,
+ 0x0C,
+ 0x18,
+ 0x03,
+ 0x60,
+ 0xE0,
+ 0x03,
+ 0x18,
+ 0x0C,
+ 0x04,
+ 0x10,
+ 0xC0,
+ 0x01,
+ 0x20,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xBB,
+ 0x6D,
+ 0x2A,
+ 0x49,
+ 0x2A,
+ 0x49,
+ 0x3A,
+ 0x49,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_codetx{
+ {16, 16},
+ bitmap_icon_codetx_data};
+
+static constexpr uint8_t bitmap_icon_controls_data[] = {
+ 0x8C,
+ 0x31,
+ 0x5A,
+ 0x6B,
+ 0xDE,
+ 0x7B,
+ 0x8C,
+ 0x31,
+ 0x00,
+ 0x00,
+ 0x8C,
+ 0x31,
+ 0x5A,
+ 0x7B,
+ 0xDE,
+ 0x7B,
+ 0x8C,
+ 0x31,
+ 0x00,
+ 0x00,
+ 0x8C,
+ 0x31,
+ 0xDA,
+ 0x7B,
+ 0xDE,
+ 0x7B,
+ 0x8C,
+ 0x31,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_controls{
+ {16, 16},
+ bitmap_icon_controls_data};
+
+static constexpr uint8_t bitmap_icon_copy_data[] = {
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0x84,
+ 0x01,
+ 0xC4,
+ 0x0F,
+ 0x74,
+ 0x18,
+ 0x44,
+ 0x38,
+ 0x44,
+ 0x78,
+ 0x74,
+ 0x40,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x74,
+ 0x5F,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x7C,
+ 0x40,
+ 0xC0,
+ 0x7F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_copy{
+ {16, 16},
+ bitmap_icon_copy_data};
+
+static constexpr uint8_t bitmap_icon_cut_data[] = {
+ 0x00,
+ 0x00,
+ 0x10,
+ 0x10,
+ 0x30,
+ 0x18,
+ 0x20,
+ 0x08,
+ 0x60,
+ 0x0C,
+ 0x40,
+ 0x04,
+ 0xC0,
+ 0x06,
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x03,
+ 0x78,
+ 0x1E,
+ 0x44,
+ 0x22,
+ 0x44,
+ 0x22,
+ 0x44,
+ 0x22,
+ 0x38,
+ 0x1C,
+};
+static constexpr Bitmap bitmap_icon_cut{
+ {16, 16},
+ bitmap_icon_cut_data};
+
+static constexpr uint8_t bitmap_icon_cwgen_data[] = {
+ 0x18,
+ 0x00,
+ 0x24,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x42,
+ 0x00,
+ 0x81,
+ 0x00,
+ 0xAB,
+ 0x6A,
+ 0x80,
+ 0x40,
+ 0x00,
+ 0x21,
+ 0x00,
+ 0x21,
+ 0x00,
+ 0x21,
+ 0x00,
+ 0x21,
+ 0x00,
+ 0x12,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_cwgen{
+ {16, 16},
+ bitmap_icon_cwgen_data};
+
+static constexpr uint8_t bitmap_icon_debug_data[] = {
+ 0xFE,
+ 0x03,
+ 0x02,
+ 0x07,
+ 0x2A,
+ 0x0D,
+ 0x52,
+ 0x0F,
+ 0x2A,
+ 0x08,
+ 0x52,
+ 0x09,
+ 0xAA,
+ 0x0A,
+ 0x52,
+ 0x09,
+ 0xAA,
+ 0x0A,
+ 0x52,
+ 0x01,
+ 0xAA,
+ 0x12,
+ 0x02,
+ 0x08,
+ 0x02,
+ 0xFC,
+ 0x02,
+ 0x08,
+ 0xFE,
+ 0x13,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_debug{
+ {16, 16},
+ bitmap_icon_debug_data};
+
+static constexpr uint8_t bitmap_icon_delete_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0C,
+ 0x30,
+ 0x1C,
+ 0x38,
+ 0x38,
+ 0x1C,
+ 0x70,
+ 0x0E,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0x70,
+ 0x0E,
+ 0x38,
+ 0x1C,
+ 0x1C,
+ 0x38,
+ 0x0C,
+ 0x30,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_delete{
+ {16, 16},
+ bitmap_icon_delete_data};
+
+static constexpr uint8_t bitmap_icon_dir_data[] = {
+ 0x00,
+ 0x00,
+ 0x3E,
+ 0x00,
+ 0x41,
+ 0x00,
+ 0xC1,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xAF,
+ 0xEA,
+ 0x57,
+ 0xF5,
+ 0xEF,
+ 0xEF,
+ 0xF7,
+ 0xF7,
+ 0xEE,
+ 0x6F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_dir{
+ {16, 16},
+ bitmap_icon_dir_data};
+
+static constexpr uint8_t bitmap_icon_dmr_data[] = {
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x1F,
+ 0xFE,
+ 0x3F,
+ 0x0E,
+ 0x78,
+ 0x0E,
+ 0x70,
+ 0x0E,
+ 0x70,
+ 0x0E,
+ 0x70,
+ 0x0E,
+ 0x78,
+ 0xFE,
+ 0x3F,
+ 0xFE,
+ 0x1F,
+ 0x8E,
+ 0x07,
+ 0x0E,
+ 0x0F,
+ 0x0E,
+ 0x1E,
+ 0x0E,
+ 0x3C,
+ 0x0E,
+ 0x78,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_dmr{
+ {16, 16},
+ bitmap_icon_dmr_data};
+
+static constexpr uint8_t bitmap_icon_doom_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x77,
+ 0xDF,
+ 0xFF,
+ 0xDF,
+ 0xD9,
+ 0xFD,
+ 0x89,
+ 0xF8,
+ 0x89,
+ 0xE8,
+ 0x89,
+ 0xA8,
+ 0x89,
+ 0xA8,
+ 0xD9,
+ 0xAD,
+ 0x79,
+ 0xAF,
+ 0x2D,
+ 0xAA,
+ 0x07,
+ 0xA8,
+ 0x03,
+ 0xA0,
+ 0x01,
+ 0x80,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_doom{
+ {16, 16},
+ bitmap_icon_doom_data};
+
+static constexpr uint8_t bitmap_icon_downconvert_data[] = {
+ 0x00,
+ 0x00,
+ 0x77,
+ 0x77,
+ 0x51,
+ 0x51,
+ 0x33,
+ 0x53,
+ 0x51,
+ 0x51,
+ 0x51,
+ 0x77,
+ 0x00,
+ 0x80,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xF8,
+ 0x1F,
+ 0xF0,
+ 0x0F,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0x80,
+ 0x01,
+};
+static constexpr Bitmap bitmap_icon_downconvert{
+ {16, 16},
+ bitmap_icon_downconvert_data};
+
+static constexpr uint8_t bitmap_icon_ert_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0F,
+ 0x80,
+ 0x7F,
+ 0xC0,
+ 0x0F,
+ 0xFC,
+ 0x0F,
+ 0xC2,
+ 0x0F,
+ 0x82,
+ 0x7F,
+ 0x01,
+ 0x0F,
+ 0x01,
+ 0x00,
+ 0x21,
+ 0x05,
+ 0x53,
+ 0x09,
+ 0x56,
+ 0x09,
+ 0x50,
+ 0x05,
+ 0x50,
+ 0x05,
+ 0x20,
+ 0xAD,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_ert{
+ {16, 16},
+ bitmap_icon_ert_data};
+
+static constexpr uint8_t bitmap_icon_file_data[] = {
+ 0xFC,
+ 0x03,
+ 0x04,
+ 0x06,
+ 0x04,
+ 0x0E,
+ 0x04,
+ 0x1E,
+ 0x04,
+ 0x3E,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0xFC,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_file{
+ {16, 16},
+ bitmap_icon_file_data};
+
+static constexpr uint8_t bitmap_icon_file_image_data[] = {
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x89,
+ 0x80,
+ 0xC1,
+ 0x81,
+ 0xE1,
+ 0xA3,
+ 0xB1,
+ 0xB3,
+ 0x89,
+ 0xDC,
+ 0x07,
+ 0x8C,
+ 0x01,
+ 0x90,
+ 0x01,
+ 0x80,
+ 0xAB,
+ 0x82,
+ 0xFF,
+ 0xD5,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_file_image{
+ {16, 16},
+ bitmap_icon_file_image_data};
+
+static constexpr uint8_t bitmap_icon_file_iq_data[] = {
+ 0xFC,
+ 0x03,
+ 0x04,
+ 0x06,
+ 0x04,
+ 0x0E,
+ 0x04,
+ 0x1E,
+ 0x04,
+ 0x3E,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x21,
+ 0x44,
+ 0x25,
+ 0x54,
+ 0x25,
+ 0xF4,
+ 0x2F,
+ 0xA4,
+ 0x2A,
+ 0x84,
+ 0x22,
+ 0x04,
+ 0x22,
+ 0x04,
+ 0x20,
+ 0xFC,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_file_iq{
+ {16, 16},
+ bitmap_icon_file_iq_data};
+
+static constexpr uint8_t bitmap_icon_file_text_data[] = {
+ 0xFC,
+ 0x03,
+ 0x04,
+ 0x06,
+ 0x04,
+ 0x0E,
+ 0x04,
+ 0x1E,
+ 0xF4,
+ 0x3E,
+ 0x04,
+ 0x20,
+ 0xF4,
+ 0x2F,
+ 0x04,
+ 0x20,
+ 0xF4,
+ 0x2F,
+ 0x04,
+ 0x20,
+ 0xF4,
+ 0x2F,
+ 0x04,
+ 0x20,
+ 0xF4,
+ 0x2F,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0xFC,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_file_text{
+ {16, 16},
+ bitmap_icon_file_text_data};
+
+static constexpr uint8_t bitmap_icon_file_wav_data[] = {
+ 0xFC,
+ 0x03,
+ 0x04,
+ 0x06,
+ 0x04,
+ 0x0E,
+ 0x04,
+ 0x1E,
+ 0x04,
+ 0x3E,
+ 0x84,
+ 0x20,
+ 0xC4,
+ 0x22,
+ 0xF4,
+ 0x20,
+ 0xF4,
+ 0x2E,
+ 0xF4,
+ 0x20,
+ 0xC4,
+ 0x22,
+ 0x84,
+ 0x24,
+ 0x04,
+ 0x28,
+ 0x04,
+ 0x20,
+ 0x04,
+ 0x20,
+ 0xFC,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_file_wav{
+ {16, 16},
+ bitmap_icon_file_wav_data};
+
+static constexpr uint8_t bitmap_icon_font_viewer_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x10,
+ 0x0C,
+ 0x38,
+ 0x0C,
+ 0x38,
+ 0x0C,
+ 0x6C,
+ 0x0C,
+ 0x6C,
+ 0x0C,
+ 0xC6,
+ 0x7C,
+ 0xFE,
+ 0xFC,
+ 0xFF,
+ 0x8D,
+ 0x83,
+ 0x8D,
+ 0x83,
+ 0xFD,
+ 0x01,
+ 0x7D,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_font_viewer{
+ {16, 16},
+ bitmap_icon_font_viewer_data};
+
+static constexpr uint8_t bitmap_icon_fox_data[] = {
+ 0x18,
+ 0x18,
+ 0x28,
+ 0x14,
+ 0x68,
+ 0x16,
+ 0x68,
+ 0x16,
+ 0xC8,
+ 0x13,
+ 0x88,
+ 0x11,
+ 0x04,
+ 0x20,
+ 0x24,
+ 0x24,
+ 0x22,
+ 0x44,
+ 0x01,
+ 0x80,
+ 0x06,
+ 0x60,
+ 0x98,
+ 0x19,
+ 0x20,
+ 0x04,
+ 0x40,
+ 0x02,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_fox{
+ {16, 16},
+ bitmap_icon_fox_data};
+
+static constexpr uint8_t bitmap_icon_freqman_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7E,
+ 0x7E,
+ 0x81,
+ 0x81,
+ 0xBD,
+ 0xBD,
+ 0x81,
+ 0x81,
+ 0xBD,
+ 0xBD,
+ 0x81,
+ 0x81,
+ 0xBD,
+ 0x9D,
+ 0x81,
+ 0x81,
+ 0xBD,
+ 0xE1,
+ 0x81,
+ 0x61,
+ 0x7E,
+ 0x3E,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_freqman{
+ {16, 16},
+ bitmap_icon_freqman_data};
+
+static constexpr uint8_t bitmap_icon_games_data[] = {
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0xFE,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xE7,
+ 0xD7,
+ 0xE7,
+ 0x93,
+ 0x81,
+ 0xFF,
+ 0x81,
+ 0x93,
+ 0xE7,
+ 0xD7,
+ 0xE7,
+ 0xFF,
+ 0x3F,
+ 0xF9,
+ 0xFE,
+ 0x7F,
+};
+static constexpr Bitmap bitmap_icon_games{
+ {16, 16},
+ bitmap_icon_games_data};
+
+static constexpr uint8_t bitmap_icon_gps_sim_data[] = {
+ 0xC0,
+ 0x07,
+ 0xE0,
+ 0x0F,
+ 0x70,
+ 0x1F,
+ 0x78,
+ 0x3E,
+ 0x78,
+ 0x3C,
+ 0x78,
+ 0x38,
+ 0x78,
+ 0x30,
+ 0x78,
+ 0x38,
+ 0x78,
+ 0x3C,
+ 0x70,
+ 0x1E,
+ 0x70,
+ 0x1F,
+ 0xE0,
+ 0x0F,
+ 0xC0,
+ 0x07,
+ 0x80,
+ 0x03,
+ 0x20,
+ 0x09,
+ 0x50,
+ 0x14,
+};
+static constexpr Bitmap bitmap_icon_gps_sim{
+ {16, 16},
+ bitmap_icon_gps_sim_data};
+
+static constexpr uint8_t bitmap_icon_hackrf_data[] = {
+ 0xF0,
+ 0x0F,
+ 0x10,
+ 0x08,
+ 0x50,
+ 0x0A,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0x10,
+ 0x08,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF0,
+ 0x0F,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+};
+static constexpr Bitmap bitmap_icon_hackrf{
+ {16, 16},
+ bitmap_icon_hackrf_data};
+
+static constexpr uint8_t bitmap_icon_hide_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x00,
+ 0x20,
+ 0xE0,
+ 0x17,
+ 0x18,
+ 0x18,
+ 0xC4,
+ 0x27,
+ 0x62,
+ 0x42,
+ 0x21,
+ 0x85,
+ 0xA1,
+ 0x84,
+ 0x62,
+ 0x46,
+ 0xA4,
+ 0x23,
+ 0x18,
+ 0x18,
+ 0xE8,
+ 0x07,
+ 0x04,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_hide{
+ {16, 16},
+ bitmap_icon_hide_data};
+
+static constexpr uint8_t bitmap_icon_jammer_data[] = {
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0x1C,
+ 0x38,
+ 0x0E,
+ 0x78,
+ 0x06,
+ 0x7C,
+ 0x03,
+ 0xCE,
+ 0x03,
+ 0xC7,
+ 0x83,
+ 0xC3,
+ 0xC3,
+ 0xC1,
+ 0xE3,
+ 0xC0,
+ 0x73,
+ 0xC0,
+ 0x3E,
+ 0x60,
+ 0x1E,
+ 0x70,
+ 0x1C,
+ 0x38,
+ 0xF8,
+ 0x1F,
+ 0xE0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_jammer{
+ {16, 16},
+ bitmap_icon_jammer_data};
+
+static constexpr uint8_t bitmap_icon_lcr_data[] = {
+ 0x0C,
+ 0x00,
+ 0xFF,
+ 0x7F,
+ 0x01,
+ 0x80,
+ 0xC1,
+ 0x9B,
+ 0xFF,
+ 0x7F,
+ 0x0C,
+ 0x00,
+ 0xFF,
+ 0x7F,
+ 0x01,
+ 0x80,
+ 0xC1,
+ 0x9D,
+ 0xFF,
+ 0x7F,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_lcr{
+ {16, 16},
+ bitmap_icon_lcr_data};
+
+static constexpr uint8_t bitmap_icon_lge_data[] = {
+ 0x00,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0xA4,
+ 0x12,
+ 0xA8,
+ 0x0A,
+ 0xD0,
+ 0x05,
+ 0xEC,
+ 0x1B,
+ 0xF0,
+ 0x07,
+ 0xFE,
+ 0xFF,
+ 0xF0,
+ 0x07,
+ 0xEC,
+ 0x1B,
+ 0xD0,
+ 0x05,
+ 0xA8,
+ 0x0A,
+ 0xA4,
+ 0x12,
+ 0x80,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_lge{
+ {16, 16},
+ bitmap_icon_lge_data};
+
+static constexpr uint8_t bitmap_icon_load_data[] = {
+ 0x00,
+ 0x01,
+ 0x80,
+ 0x03,
+ 0x40,
+ 0x05,
+ 0x00,
+ 0x01,
+ 0x0E,
+ 0x01,
+ 0x11,
+ 0x01,
+ 0x7F,
+ 0x1D,
+ 0x01,
+ 0x20,
+ 0xF9,
+ 0xFF,
+ 0xF9,
+ 0xFF,
+ 0xFD,
+ 0x7F,
+ 0xFD,
+ 0x7F,
+ 0xFF,
+ 0x3F,
+ 0xFF,
+ 0x3F,
+ 0xFF,
+ 0x1F,
+ 0xFF,
+ 0x1F,
+};
+static constexpr Bitmap bitmap_icon_load{
+ {16, 16},
+ bitmap_icon_load_data};
+
+static constexpr uint8_t bitmap_icon_looking_data[] = {
+ 0xF8,
+ 0x01,
+ 0xFC,
+ 0x03,
+ 0x0E,
+ 0x07,
+ 0x07,
+ 0x0E,
+ 0xF3,
+ 0x0C,
+ 0x9F,
+ 0x0F,
+ 0x9F,
+ 0x0F,
+ 0xF3,
+ 0x0C,
+ 0x07,
+ 0x0E,
+ 0x0E,
+ 0x07,
+ 0xFC,
+ 0x1F,
+ 0xF8,
+ 0x3D,
+ 0x00,
+ 0x7C,
+ 0x00,
+ 0xF8,
+ 0x00,
+ 0xF0,
+ 0x00,
+ 0x60,
+};
+static constexpr Bitmap bitmap_icon_looking{
+ {16, 16},
+ bitmap_icon_looking_data};
+
+static constexpr uint8_t bitmap_icon_lora_data[] = {
+ 0xC0,
+ 0x03,
+ 0x30,
+ 0x0C,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x03,
+ 0x60,
+ 0x06,
+ 0x60,
+ 0x06,
+ 0x60,
+ 0x06,
+ 0x60,
+ 0x06,
+ 0xC0,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x30,
+ 0x0C,
+ 0xC0,
+ 0x03,
+};
+static constexpr Bitmap bitmap_icon_lora{
+ {16, 16},
+ bitmap_icon_lora_data};
+
+static constexpr uint8_t bitmap_icon_memory_data[] = {
+ 0x54,
+ 0x15,
+ 0x54,
+ 0x15,
+ 0xFF,
+ 0x7F,
+ 0xFC,
+ 0x1F,
+ 0xFF,
+ 0x7F,
+ 0xCC,
+ 0x19,
+ 0xAF,
+ 0x7A,
+ 0x6C,
+ 0x1B,
+ 0xEF,
+ 0x7B,
+ 0xEC,
+ 0x1B,
+ 0xFF,
+ 0x7F,
+ 0xFC,
+ 0x1F,
+ 0xFF,
+ 0x7F,
+ 0x54,
+ 0x15,
+ 0x54,
+ 0x15,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_memory{
+ {16, 16},
+ bitmap_icon_memory_data};
+
+static constexpr uint8_t bitmap_icon_microphone_data[] = {
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xE0,
+ 0x07,
+ 0xE0,
+ 0x07,
+ 0xE8,
+ 0x17,
+ 0xE8,
+ 0x17,
+ 0xE8,
+ 0x17,
+ 0xE8,
+ 0x17,
+ 0xE8,
+ 0x17,
+ 0xC8,
+ 0x13,
+ 0x18,
+ 0x18,
+ 0xF0,
+ 0x0F,
+ 0xC0,
+ 0x03,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xE0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_microphone{
+ {16, 16},
+ bitmap_icon_microphone_data};
+
+static constexpr uint8_t bitmap_icon_modem_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xF8,
+ 0x1F,
+ 0x04,
+ 0x20,
+ 0x02,
+ 0x40,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xAB,
+ 0xDF,
+ 0xAB,
+ 0xDF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_modem{
+ {16, 16},
+ bitmap_icon_modem_data};
+
+static constexpr uint8_t bitmap_icon_morse_data[] = {
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xBB,
+ 0xD0,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x0B,
+ 0xE1,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xEB,
+ 0xD0,
+ 0xFF,
+ 0xFF,
+ 0xFE,
+ 0x7F,
+ 0x70,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x10,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_morse{
+ {16, 16},
+ bitmap_icon_morse_data};
+
+static constexpr uint8_t bitmap_icon_new_category_data[] = {
+ 0x00,
+ 0x18,
+ 0x3E,
+ 0x18,
+ 0x41,
+ 0x7E,
+ 0xC1,
+ 0x7E,
+ 0xFF,
+ 0x18,
+ 0xFF,
+ 0xDB,
+ 0xFF,
+ 0xC3,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xAF,
+ 0xEA,
+ 0x57,
+ 0xF5,
+ 0xEF,
+ 0xEF,
+ 0xF7,
+ 0xF7,
+ 0xEE,
+ 0x6F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_new_category{
+ {16, 16},
+ bitmap_icon_new_category_data};
+
+static constexpr uint8_t bitmap_icon_new_dir_data[] = {
+ 0x00,
+ 0x00,
+ 0x1E,
+ 0x00,
+ 0x21,
+ 0x00,
+ 0xE1,
+ 0x7F,
+ 0x01,
+ 0xC0,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0xF1,
+ 0x8F,
+ 0xF1,
+ 0x8F,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x03,
+ 0xC0,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_new_dir{
+ {16, 16},
+ bitmap_icon_new_dir_data};
+
static constexpr uint8_t bitmap_icon_new_file_data[] = {
0x00,
0x00,
@@ -1101,6 +2435,2392 @@ static constexpr Bitmap bitmap_icon_new_file{
{16, 16},
bitmap_icon_new_file_data};
+static constexpr uint8_t bitmap_icon_noaa_data[] = {
+ 0x1C,
+ 0x80,
+ 0x3C,
+ 0x40,
+ 0x78,
+ 0x18,
+ 0xF0,
+ 0x20,
+ 0xE0,
+ 0x26,
+ 0x00,
+ 0x0F,
+ 0x80,
+ 0x0F,
+ 0xC0,
+ 0x07,
+ 0xE1,
+ 0x1B,
+ 0xC5,
+ 0x39,
+ 0x95,
+ 0x78,
+ 0x35,
+ 0xF0,
+ 0x09,
+ 0xE0,
+ 0x72,
+ 0xC0,
+ 0x04,
+ 0x00,
+ 0x78,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_noaa{
+ {16, 16},
+ bitmap_icon_noaa_data};
+
+static constexpr uint8_t bitmap_icon_notepad_data[] = {
+ 0x0C,
+ 0x00,
+ 0x1E,
+ 0x00,
+ 0x2F,
+ 0x00,
+ 0x47,
+ 0x00,
+ 0xE2,
+ 0x00,
+ 0xD4,
+ 0x01,
+ 0xB8,
+ 0x03,
+ 0x70,
+ 0x07,
+ 0xE0,
+ 0x0E,
+ 0xC0,
+ 0x1D,
+ 0x80,
+ 0x3B,
+ 0x00,
+ 0x4F,
+ 0x00,
+ 0x46,
+ 0x00,
+ 0x84,
+ 0x00,
+ 0xD8,
+ 0x00,
+ 0xE0,
+};
+static constexpr Bitmap bitmap_icon_notepad{
+ {16, 16},
+ bitmap_icon_notepad_data};
+
+static constexpr uint8_t bitmap_icon_nrf_data[] = {
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0xF8,
+ 0x3F,
+ 0xFC,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0xDC,
+ 0x7F,
+ 0x8C,
+ 0x6B,
+ 0xDC,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0xF8,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_nrf{
+ {16, 16},
+ bitmap_icon_nrf_data};
+
+static constexpr uint8_t bitmap_icon_nuoptix_data[] = {
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x40,
+ 0x02,
+ 0x40,
+ 0x1A,
+ 0x40,
+ 0x1A,
+ 0x20,
+ 0x0C,
+ 0x20,
+ 0x0F,
+ 0x20,
+ 0x1E,
+ 0x10,
+ 0x0E,
+ 0x10,
+ 0x0B,
+ 0x10,
+ 0x0B,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_nuoptix{
+ {16, 16},
+ bitmap_icon_nuoptix_data};
+
+static constexpr uint8_t bitmap_icon_options_datetime_data[] = {
+ 0x0C,
+ 0x06,
+ 0xFF,
+ 0x1F,
+ 0x49,
+ 0x12,
+ 0x49,
+ 0x12,
+ 0xFF,
+ 0x1F,
+ 0x49,
+ 0x00,
+ 0x49,
+ 0x1C,
+ 0x7F,
+ 0x63,
+ 0x09,
+ 0x49,
+ 0x89,
+ 0x88,
+ 0xBE,
+ 0xB8,
+ 0x80,
+ 0x80,
+ 0x00,
+ 0x41,
+ 0x00,
+ 0x63,
+ 0x00,
+ 0x1C,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_options_datetime{
+ {16, 16},
+ bitmap_icon_options_datetime_data};
+
+static constexpr uint8_t bitmap_icon_options_radio_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x20,
+ 0x12,
+ 0x48,
+ 0x8A,
+ 0x51,
+ 0xCA,
+ 0x53,
+ 0xCA,
+ 0x53,
+ 0x8A,
+ 0x51,
+ 0x12,
+ 0x48,
+ 0x84,
+ 0x21,
+ 0xC0,
+ 0x03,
+ 0x40,
+ 0x02,
+ 0x60,
+ 0x06,
+ 0x20,
+ 0x04,
+ 0x30,
+ 0x0C,
+ 0xF0,
+ 0x0F,
+};
+static constexpr Bitmap bitmap_icon_options_radio{
+ {16, 16},
+ bitmap_icon_options_radio_data};
+
+static constexpr uint8_t bitmap_icon_options_touch_data[] = {
+ 0xC7,
+ 0xF1,
+ 0x97,
+ 0xF4,
+ 0x27,
+ 0xF2,
+ 0x8F,
+ 0xF8,
+ 0x5F,
+ 0xFD,
+ 0x47,
+ 0xFD,
+ 0x53,
+ 0xC1,
+ 0x4B,
+ 0x9F,
+ 0x43,
+ 0xB5,
+ 0x6F,
+ 0xA0,
+ 0x2F,
+ 0xA0,
+ 0x20,
+ 0x20,
+ 0x60,
+ 0x20,
+ 0x40,
+ 0x10,
+ 0xC0,
+ 0x1F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_options_touch{
+ {16, 16},
+ bitmap_icon_options_touch_data};
+
+static constexpr uint8_t bitmap_icon_options_ui_data[] = {
+ 0xFF,
+ 0x1F,
+ 0xFF,
+ 0x13,
+ 0xFF,
+ 0x1F,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x04,
+ 0x01,
+ 0x0C,
+ 0x01,
+ 0x1C,
+ 0x01,
+ 0x3C,
+ 0xFF,
+ 0x7D,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0x34,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0x60,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_options_ui{
+ {16, 16},
+ bitmap_icon_options_ui_data};
+
+static constexpr uint8_t bitmap_icon_paint_data[] = {
+ 0xFE,
+ 0x3F,
+ 0xFF,
+ 0x3F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xBF,
+ 0xFE,
+ 0xBF,
+ 0x00,
+ 0x80,
+ 0x80,
+ 0xFF,
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+};
+static constexpr Bitmap bitmap_icon_paint{
+ {16, 16},
+ bitmap_icon_paint_data};
+
+static constexpr uint8_t bitmap_icon_paste_data[] = {
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x00,
+ 0x18,
+ 0x03,
+ 0xE4,
+ 0x04,
+ 0x04,
+ 0x04,
+ 0x04,
+ 0x04,
+ 0x84,
+ 0x3F,
+ 0x84,
+ 0x20,
+ 0x84,
+ 0x2E,
+ 0x84,
+ 0x20,
+ 0x84,
+ 0x2E,
+ 0x84,
+ 0x20,
+ 0x84,
+ 0x2E,
+ 0xF8,
+ 0x20,
+ 0x80,
+ 0x3F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_paste{
+ {16, 16},
+ bitmap_icon_paste_data};
+
+static constexpr uint8_t bitmap_icon_peripherals_data[] = {
+ 0x54,
+ 0x01,
+ 0x54,
+ 0x01,
+ 0xFF,
+ 0x07,
+ 0x7C,
+ 0x01,
+ 0xBF,
+ 0x07,
+ 0xDC,
+ 0x18,
+ 0x6F,
+ 0x10,
+ 0x2C,
+ 0x21,
+ 0xAF,
+ 0x20,
+ 0x34,
+ 0x20,
+ 0x54,
+ 0x10,
+ 0xC0,
+ 0x38,
+ 0x00,
+ 0x77,
+ 0x00,
+ 0xE0,
+ 0x00,
+ 0xC0,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_peripherals{
+ {16, 16},
+ bitmap_icon_peripherals_data};
+
+static constexpr uint8_t bitmap_icon_peripherals_details_data[] = {
+ 0x54,
+ 0x01,
+ 0x54,
+ 0x01,
+ 0xFF,
+ 0x07,
+ 0xFC,
+ 0x01,
+ 0x3F,
+ 0x00,
+ 0xBC,
+ 0x3F,
+ 0xBF,
+ 0x60,
+ 0xBC,
+ 0xEE,
+ 0xBF,
+ 0x80,
+ 0x94,
+ 0xBE,
+ 0x94,
+ 0x80,
+ 0x80,
+ 0xBE,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0xBE,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_peripherals_details{
+ {16, 16},
+ bitmap_icon_peripherals_details_data};
+
+static constexpr uint8_t bitmap_icon_pocsag_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0x3F,
+ 0xFE,
+ 0x7F,
+ 0x02,
+ 0x40,
+ 0xBA,
+ 0x45,
+ 0x02,
+ 0x40,
+ 0xFE,
+ 0x7F,
+ 0xFE,
+ 0x7F,
+ 0x92,
+ 0x7C,
+ 0x92,
+ 0x7C,
+ 0xFC,
+ 0x3F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_pocsag{
+ {16, 16},
+ bitmap_icon_pocsag_data};
+
+static constexpr uint8_t bitmap_icon_previous_data[] = {
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x00,
+ 0xE0,
+ 0x00,
+ 0x70,
+ 0x00,
+ 0x38,
+ 0x00,
+ 0x1C,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x0E,
+ 0x00,
+ 0x1C,
+ 0x00,
+ 0x38,
+ 0x00,
+ 0x70,
+ 0x00,
+ 0xE0,
+ 0x00,
+ 0xC0,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_previous{
+ {16, 16},
+ bitmap_icon_previous_data};
+
+static constexpr uint8_t bitmap_icon_protoview_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xF8,
+ 0x87,
+ 0x08,
+ 0x84,
+ 0x0F,
+ 0xFC,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xF3,
+ 0xE0,
+ 0x92,
+ 0xA0,
+ 0x9E,
+ 0xBF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0xF3,
+ 0x04,
+ 0x12,
+ 0x07,
+ 0x1E,
+};
+static constexpr Bitmap bitmap_icon_protoview{
+ {16, 16},
+ bitmap_icon_protoview_data};
+
+static constexpr uint8_t bitmap_icon_qr_code_data[] = {
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x7E,
+ 0xC6,
+ 0x62,
+ 0xFA,
+ 0x5A,
+ 0xFA,
+ 0x5A,
+ 0xDA,
+ 0x5A,
+ 0xFE,
+ 0x7E,
+ 0x7E,
+ 0x7E,
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x46,
+ 0xC2,
+ 0x06,
+ 0xFA,
+ 0x18,
+ 0xFA,
+ 0x18,
+ 0xC6,
+ 0x60,
+ 0xFE,
+ 0x62,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_qr_code{
+ {16, 16},
+ bitmap_icon_qr_code_data};
+
+static constexpr uint8_t bitmap_icon_rds_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3C,
+ 0x3C,
+ 0x7E,
+ 0x7E,
+ 0x67,
+ 0xE7,
+ 0x83,
+ 0xC3,
+ 0xC7,
+ 0xE1,
+ 0xFD,
+ 0xBC,
+ 0x42,
+ 0x42,
+ 0x3C,
+ 0x3C,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_rds{
+ {16, 16},
+ bitmap_icon_rds_data};
+
+static constexpr uint8_t bitmap_icon_receivers_data[] = {
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x98,
+ 0x19,
+ 0xB0,
+ 0x0D,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0x83,
+ 0xC1,
+ 0x03,
+ 0xC0,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_receivers{
+ {16, 16},
+ bitmap_icon_receivers_data};
+
+static constexpr uint8_t bitmap_icon_remote_data[] = {
+ 0x20,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0xE0,
+ 0x07,
+ 0xF0,
+ 0x0F,
+ 0x30,
+ 0x0C,
+ 0x30,
+ 0x0C,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0x70,
+ 0x0D,
+ 0xB0,
+ 0x0E,
+ 0x70,
+ 0x0D,
+ 0xB0,
+ 0x0E,
+ 0xF0,
+ 0x0F,
+ 0xE0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_remote{
+ {16, 16},
+ bitmap_icon_remote_data};
+
+static constexpr uint8_t bitmap_icon_rename_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0x04,
+ 0xFF,
+ 0xF5,
+ 0x01,
+ 0x84,
+ 0xC9,
+ 0x84,
+ 0x55,
+ 0x85,
+ 0xDD,
+ 0x84,
+ 0x55,
+ 0x85,
+ 0xD5,
+ 0x84,
+ 0x01,
+ 0x84,
+ 0xFF,
+ 0xF5,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_rename{
+ {16, 16},
+ bitmap_icon_rename_data};
+
+static constexpr uint8_t bitmap_icon_rename_numeric_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0x04,
+ 0xFF,
+ 0xF5,
+ 0x01,
+ 0x84,
+ 0xC9,
+ 0x85,
+ 0x0D,
+ 0x85,
+ 0xC9,
+ 0x85,
+ 0x49,
+ 0x84,
+ 0xDD,
+ 0x85,
+ 0x01,
+ 0x84,
+ 0xFF,
+ 0xF5,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_rename_numeric{
+ {16, 16},
+ bitmap_icon_rename_numeric_data};
+
+static constexpr uint8_t bitmap_icon_replay_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0xFC,
+ 0x03,
+ 0xFC,
+ 0x0F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x0F,
+ 0xFC,
+ 0x03,
+ 0xFC,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_replay{
+ {16, 16},
+ bitmap_icon_replay_data};
+
+static constexpr uint8_t bitmap_icon_save_data[] = {
+ 0xFC,
+ 0x07,
+ 0x0A,
+ 0x0A,
+ 0x0A,
+ 0x12,
+ 0xF2,
+ 0x21,
+ 0x02,
+ 0x20,
+ 0x02,
+ 0x20,
+ 0x02,
+ 0x20,
+ 0x02,
+ 0x20,
+ 0xFA,
+ 0x27,
+ 0xFA,
+ 0x2F,
+ 0x0A,
+ 0x28,
+ 0xFA,
+ 0x2F,
+ 0x0A,
+ 0x28,
+ 0xFA,
+ 0x2F,
+ 0xFC,
+ 0x1F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_save{
+ {16, 16},
+ bitmap_icon_save_data};
+
+static constexpr uint8_t bitmap_icon_scanner_data[] = {
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xC3,
+ 0x00,
+ 0xE0,
+ 0xFF,
+ 0xEF,
+ 0xFF,
+ 0xC0,
+ 0x00,
+ 0x83,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_scanner{
+ {16, 16},
+ bitmap_icon_scanner_data};
+
+static constexpr uint8_t bitmap_icon_script_data[] = {
+ 0xFC,
+ 0x07,
+ 0xFA,
+ 0x0F,
+ 0x19,
+ 0x1A,
+ 0xF9,
+ 0x1F,
+ 0x1E,
+ 0x1D,
+ 0xF8,
+ 0x1F,
+ 0x98,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x08,
+ 0x14,
+ 0xF8,
+ 0x1F,
+ 0x48,
+ 0x1E,
+ 0xF8,
+ 0xFF,
+ 0x78,
+ 0x80,
+ 0x30,
+ 0x40,
+ 0xE0,
+ 0x3F,
+};
+static constexpr Bitmap bitmap_icon_script{
+ {16, 16},
+ bitmap_icon_script_data};
+
+static constexpr uint8_t bitmap_icon_sd_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x1F,
+ 0xE0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x98,
+ 0x18,
+ 0xE8,
+ 0x16,
+ 0xC8,
+ 0x16,
+ 0x98,
+ 0x16,
+ 0xB8,
+ 0x16,
+ 0xC8,
+ 0x18,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_sd{
+ {16, 16},
+ bitmap_icon_sd_data};
+
+static constexpr uint8_t bitmap_icon_sdcard_data[] = {
+ 0xF0,
+ 0x3F,
+ 0x58,
+ 0x35,
+ 0x5C,
+ 0x35,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xAC,
+ 0x3A,
+ 0x5C,
+ 0x35,
+ 0xAC,
+ 0x3A,
+ 0x5C,
+ 0x35,
+ 0xAC,
+ 0x3A,
+ 0x5C,
+ 0x35,
+ 0xAC,
+ 0x3A,
+};
+static constexpr Bitmap bitmap_icon_sdcard{
+ {16, 16},
+ bitmap_icon_sdcard_data};
+
+static constexpr uint8_t bitmap_icon_search_data[] = {
+ 0xF8,
+ 0x01,
+ 0xFC,
+ 0x03,
+ 0x0E,
+ 0x07,
+ 0x07,
+ 0x0E,
+ 0x03,
+ 0x0C,
+ 0x0B,
+ 0x0C,
+ 0x0B,
+ 0x0C,
+ 0x13,
+ 0x0C,
+ 0x07,
+ 0x0E,
+ 0x0E,
+ 0x07,
+ 0xFC,
+ 0x1F,
+ 0xF8,
+ 0x3D,
+ 0x00,
+ 0x7C,
+ 0x00,
+ 0xF8,
+ 0x00,
+ 0xF0,
+ 0x00,
+ 0x60,
+};
+static constexpr Bitmap bitmap_icon_search{
+ {16, 16},
+ bitmap_icon_search_data};
+
+static constexpr uint8_t bitmap_icon_setup_data[] = {
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xE4,
+ 0x13,
+ 0xFE,
+ 0x3F,
+ 0xFF,
+ 0x7F,
+ 0x3E,
+ 0x3E,
+ 0x1C,
+ 0x1C,
+ 0x1C,
+ 0x1C,
+ 0x1C,
+ 0x1C,
+ 0x3E,
+ 0x3E,
+ 0xFF,
+ 0x7F,
+ 0xFE,
+ 0x3F,
+ 0xE4,
+ 0x13,
+ 0xC0,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_setup{
+ {16, 16},
+ bitmap_icon_setup_data};
+
+static constexpr uint8_t bitmap_icon_shift_data[] = {
+ 0x00,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0xC0,
+ 0x01,
+ 0xE0,
+ 0x03,
+ 0xF0,
+ 0x07,
+ 0xF8,
+ 0x0F,
+ 0xFC,
+ 0x1F,
+ 0xE0,
+ 0x03,
+ 0xE0,
+ 0x03,
+ 0xE0,
+ 0x03,
+ 0x20,
+ 0x02,
+ 0xE0,
+ 0x03,
+ 0x20,
+ 0x02,
+ 0xE0,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_shift{
+ {16, 16},
+ bitmap_icon_shift_data};
+
+static constexpr uint8_t bitmap_icon_sleep_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x00,
+ 0x08,
+ 0x00,
+ 0x18,
+ 0x00,
+ 0x18,
+ 0x00,
+ 0x38,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x3E,
+ 0x84,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF0,
+ 0x0F,
+ 0xC0,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_sleep{
+ {16, 16},
+ bitmap_icon_sleep_data};
+
+static constexpr uint8_t bitmap_icon_snake_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x09,
+ 0x70,
+ 0xC7,
+ 0xFC,
+ 0xC9,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0xF0,
+ 0x01,
+ 0x00,
+ 0x3E,
+ 0x00,
+ 0x40,
+ 0xFC,
+ 0x40,
+ 0x02,
+ 0x3F,
+ 0x02,
+ 0x00,
+ 0x7C,
+ 0x80,
+ 0x80,
+ 0x7F,
+};
+static constexpr Bitmap bitmap_icon_snake{
+ {16, 16},
+ bitmap_icon_snake_data};
+
+static constexpr uint8_t bitmap_icon_sonde_data[] = {
+ 0x80,
+ 0x03,
+ 0xE0,
+ 0x0F,
+ 0xE0,
+ 0x0F,
+ 0xF0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xE0,
+ 0x0F,
+ 0xE0,
+ 0x0F,
+ 0x00,
+ 0x00,
+ 0x20,
+ 0x09,
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x05,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x07,
+ 0xC0,
+ 0x07,
+ 0xC0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_sonde{
+ {16, 16},
+ bitmap_icon_sonde_data};
+
+static constexpr uint8_t bitmap_icon_soundboard_data[] = {
+ 0xF0,
+ 0x0F,
+ 0x1C,
+ 0x18,
+ 0x17,
+ 0x38,
+ 0x15,
+ 0x78,
+ 0x15,
+ 0xF8,
+ 0x15,
+ 0x82,
+ 0x15,
+ 0x8B,
+ 0xD5,
+ 0x83,
+ 0xD5,
+ 0xBB,
+ 0xD5,
+ 0x83,
+ 0x15,
+ 0x8B,
+ 0x15,
+ 0x92,
+ 0x15,
+ 0xA0,
+ 0x17,
+ 0x80,
+ 0x1C,
+ 0x80,
+ 0xF0,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_soundboard{
+ {16, 16},
+ bitmap_icon_soundboard_data};
+
+static constexpr uint8_t bitmap_icon_speaker_data[] = {
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x10,
+ 0x60,
+ 0x20,
+ 0x70,
+ 0x44,
+ 0x78,
+ 0x48,
+ 0x7F,
+ 0x91,
+ 0x7F,
+ 0x92,
+ 0x7F,
+ 0x92,
+ 0x7F,
+ 0x92,
+ 0x7F,
+ 0x92,
+ 0x7F,
+ 0x92,
+ 0x7F,
+ 0x91,
+ 0x78,
+ 0x48,
+ 0x70,
+ 0x44,
+ 0x60,
+ 0x20,
+ 0x40,
+ 0x10,
+};
+static constexpr Bitmap bitmap_icon_speaker{
+ {16, 16},
+ bitmap_icon_speaker_data};
+
+static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
+ 0x40,
+ 0x10,
+ 0x60,
+ 0x20,
+ 0x70,
+ 0x44,
+ 0x7C,
+ 0x48,
+ 0x7C,
+ 0x91,
+ 0x7C,
+ 0x92,
+ 0x70,
+ 0x92,
+ 0x60,
+ 0x92,
+ 0x40,
+ 0x92,
+ 0x00,
+ 0x92,
+ 0x30,
+ 0x91,
+ 0x48,
+ 0x48,
+ 0x84,
+ 0x44,
+ 0x84,
+ 0x20,
+ 0x86,
+ 0x11,
+ 0x86,
+ 0x01,
+};
+static constexpr Bitmap bitmap_icon_speaker_and_headphones{
+ {16, 16},
+ bitmap_icon_speaker_and_headphones_data};
+
+static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
+ 0x40,
+ 0x00,
+ 0x60,
+ 0x44,
+ 0x70,
+ 0x6C,
+ 0x7C,
+ 0x38,
+ 0x7C,
+ 0x10,
+ 0x7C,
+ 0x38,
+ 0x70,
+ 0x6C,
+ 0x60,
+ 0x44,
+ 0x40,
+ 0x00,
+ 0x00,
+ 0x44,
+ 0x30,
+ 0x6C,
+ 0x48,
+ 0x38,
+ 0x84,
+ 0x10,
+ 0x84,
+ 0x38,
+ 0x86,
+ 0x6D,
+ 0x86,
+ 0x45,
+};
+static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
+ {16, 16},
+ bitmap_icon_speaker_and_headphones_mute_data};
+
+static constexpr uint8_t bitmap_icon_speaker_mute_data[] = {
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x00,
+ 0x60,
+ 0x00,
+ 0x70,
+ 0x00,
+ 0x78,
+ 0x00,
+ 0x7F,
+ 0x22,
+ 0x7F,
+ 0x36,
+ 0x7F,
+ 0x1C,
+ 0x7F,
+ 0x08,
+ 0x7F,
+ 0x1C,
+ 0x7F,
+ 0x36,
+ 0x7F,
+ 0x22,
+ 0x78,
+ 0x00,
+ 0x70,
+ 0x00,
+ 0x60,
+ 0x00,
+ 0x40,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_speaker_mute{
+ {16, 16},
+ bitmap_icon_speaker_mute_data};
+
+static constexpr uint8_t bitmap_icon_sstv_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x7F,
+ 0x03,
+ 0xC0,
+ 0x53,
+ 0xD5,
+ 0xAB,
+ 0xCA,
+ 0x53,
+ 0xD5,
+ 0xAB,
+ 0xCA,
+ 0x53,
+ 0xD5,
+ 0xAB,
+ 0xCA,
+ 0x53,
+ 0xD5,
+ 0x03,
+ 0xC0,
+ 0xFF,
+ 0xFF,
+ 0xFB,
+ 0xD7,
+ 0xFE,
+ 0x7F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_sstv{
+ {16, 16},
+ bitmap_icon_sstv_data};
+
+static constexpr uint8_t bitmap_icon_stealth_data[] = {
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x07,
+ 0x60,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0xF0,
+ 0x0F,
+ 0xF0,
+ 0x0F,
+ 0xF8,
+ 0x1E,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3E,
+ 0xFC,
+ 0x3F,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_stealth{
+ {16, 16},
+ bitmap_icon_stealth_data};
+
+static constexpr uint8_t bitmap_icon_temperature_data[] = {
+ 0x00,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x05,
+ 0xC0,
+ 0x0D,
+ 0x40,
+ 0x0D,
+ 0xD0,
+ 0x1F,
+ 0x70,
+ 0x15,
+ 0xB0,
+ 0x1A,
+ 0x58,
+ 0x35,
+ 0xB8,
+ 0x3A,
+ 0x58,
+ 0x34,
+ 0x28,
+ 0x28,
+ 0x18,
+ 0x30,
+ 0x30,
+ 0x18,
+ 0x60,
+ 0x0C,
+ 0xC0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_temperature{
+ {16, 16},
+ bitmap_icon_temperature_data};
+
+static constexpr uint8_t bitmap_icon_tetra_data[] = {
+ 0xE0,
+ 0x0F,
+ 0x18,
+ 0x38,
+ 0xE4,
+ 0x67,
+ 0x7E,
+ 0xCE,
+ 0xC7,
+ 0xCC,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0x4F,
+ 0xBA,
+ 0xB2,
+ 0x9A,
+ 0xEE,
+ 0xBA,
+ 0xB2,
+ 0x00,
+ 0x00,
+ 0x3B,
+ 0xE3,
+ 0x73,
+ 0x7E,
+ 0xC6,
+ 0x27,
+ 0x1C,
+ 0x18,
+ 0xF0,
+ 0x07,
+};
+static constexpr Bitmap bitmap_icon_tetra{
+ {16, 16},
+ bitmap_icon_tetra_data};
+
+static constexpr uint8_t bitmap_icon_tetris_data[] = {
+ 0xF8,
+ 0xFF,
+ 0x88,
+ 0x88,
+ 0x88,
+ 0x88,
+ 0x88,
+ 0x88,
+ 0xF8,
+ 0xFF,
+ 0x80,
+ 0x08,
+ 0x80,
+ 0x08,
+ 0x9F,
+ 0x08,
+ 0x91,
+ 0x0F,
+ 0x11,
+ 0x00,
+ 0x11,
+ 0x00,
+ 0xFF,
+ 0xF1,
+ 0x11,
+ 0x91,
+ 0x11,
+ 0x91,
+ 0x11,
+ 0x91,
+ 0xFF,
+ 0xF1,
+};
+static constexpr Bitmap bitmap_icon_tetris{
+ {16, 16},
+ bitmap_icon_tetris_data};
+
+static constexpr uint8_t bitmap_icon_thermometer_data[] = {
+ 0xC0,
+ 0x00,
+ 0x20,
+ 0x01,
+ 0x10,
+ 0x02,
+ 0x10,
+ 0x3A,
+ 0x10,
+ 0x02,
+ 0x10,
+ 0x1A,
+ 0x10,
+ 0x02,
+ 0xD0,
+ 0x3A,
+ 0xD0,
+ 0x02,
+ 0xD0,
+ 0x1A,
+ 0xD0,
+ 0x02,
+ 0xE8,
+ 0x05,
+ 0xE8,
+ 0x05,
+ 0xC8,
+ 0x04,
+ 0x10,
+ 0x02,
+ 0xE0,
+ 0x01,
+};
+static constexpr Bitmap bitmap_icon_thermometer{
+ {16, 16},
+ bitmap_icon_thermometer_data};
+
+static constexpr uint8_t bitmap_icon_tools_antenna_data[] = {
+ 0x38,
+ 0x3E,
+ 0x10,
+ 0x22,
+ 0x10,
+ 0x26,
+ 0x10,
+ 0x22,
+ 0x10,
+ 0x2E,
+ 0x10,
+ 0x22,
+ 0x10,
+ 0x26,
+ 0x10,
+ 0x22,
+ 0x38,
+ 0x2E,
+ 0x38,
+ 0x22,
+ 0x38,
+ 0x26,
+ 0x38,
+ 0x22,
+ 0x38,
+ 0x2E,
+ 0x38,
+ 0x22,
+ 0x38,
+ 0x3E,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_tools_antenna{
+ {16, 16},
+ bitmap_icon_tools_antenna_data};
+
+static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = {
+ 0xF0,
+ 0x3F,
+ 0x58,
+ 0x35,
+ 0x5C,
+ 0x35,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0x3C,
+ 0x1C,
+ 0xBC,
+ 0xC9,
+ 0xBC,
+ 0xE3,
+ 0x2C,
+ 0x77,
+ 0x5C,
+ 0x3E,
+ 0xAC,
+ 0x1C,
+ 0x5C,
+ 0x3E,
+ 0x2C,
+ 0x77,
+ 0x9C,
+ 0xE3,
+ 0xAC,
+ 0xC1,
+};
+static constexpr Bitmap bitmap_icon_tools_wipesd{
+ {16, 16},
+ bitmap_icon_tools_wipesd_data};
+
+static constexpr uint8_t bitmap_icon_touchtunes_data[] = {
+ 0xE0,
+ 0x07,
+ 0x30,
+ 0x0C,
+ 0x7C,
+ 0x3E,
+ 0xC4,
+ 0x23,
+ 0x26,
+ 0x64,
+ 0x12,
+ 0x48,
+ 0x0F,
+ 0xF3,
+ 0x09,
+ 0x95,
+ 0x0F,
+ 0xF1,
+ 0x09,
+ 0x91,
+ 0x0F,
+ 0xF1,
+ 0xC9,
+ 0x91,
+ 0xE9,
+ 0x91,
+ 0xC9,
+ 0x90,
+ 0x0F,
+ 0xF0,
+ 0xFF,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_touchtunes{
+ {16, 16},
+ bitmap_icon_touchtunes_data};
+
+static constexpr uint8_t bitmap_icon_tpms_data[] = {
+ 0xC0,
+ 0x03,
+ 0xF0,
+ 0x0F,
+ 0x18,
+ 0x18,
+ 0xEC,
+ 0x37,
+ 0x36,
+ 0x6D,
+ 0x3A,
+ 0x59,
+ 0x4B,
+ 0xD5,
+ 0x8B,
+ 0xD3,
+ 0xCB,
+ 0xD1,
+ 0xAB,
+ 0xD2,
+ 0x9A,
+ 0x5C,
+ 0xB6,
+ 0x6C,
+ 0xEC,
+ 0x37,
+ 0x18,
+ 0x18,
+ 0xF0,
+ 0x0F,
+ 0xC0,
+ 0x03,
+};
+static constexpr Bitmap bitmap_icon_tpms{
+ {16, 16},
+ bitmap_icon_tpms_data};
+
+static constexpr uint8_t bitmap_icon_tranceivers_data[] = {
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xB0,
+ 0x0D,
+ 0x98,
+ 0x19,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x98,
+ 0x19,
+ 0xB0,
+ 0x0D,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0x83,
+ 0xC1,
+ 0x03,
+ 0xC0,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_tranceivers{
+ {16, 16},
+ bitmap_icon_tranceivers_data};
+
+static constexpr uint8_t bitmap_icon_transmit_data[] = {
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xB0,
+ 0x0D,
+ 0x98,
+ 0x19,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x83,
+ 0xC1,
+ 0x03,
+ 0xC0,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_icon_transmit{
+ {16, 16},
+ bitmap_icon_transmit_data};
+
+static constexpr uint8_t bitmap_icon_trash_data[] = {
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x01,
+ 0x20,
+ 0x02,
+ 0xFC,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0xA8,
+ 0x0A,
+ 0x08,
+ 0x08,
+ 0xF0,
+ 0x07,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_trash{
+ {16, 16},
+ bitmap_icon_trash_data};
+
+static constexpr uint8_t bitmap_icon_trim_data[] = {
+ 0x10,
+ 0x10,
+ 0x30,
+ 0x12,
+ 0x50,
+ 0x15,
+ 0x50,
+ 0x15,
+ 0x50,
+ 0x15,
+ 0x52,
+ 0x95,
+ 0x56,
+ 0xD5,
+ 0x5F,
+ 0xF5,
+ 0x56,
+ 0xD5,
+ 0x52,
+ 0x95,
+ 0x50,
+ 0x15,
+ 0x50,
+ 0x15,
+ 0x50,
+ 0x15,
+ 0x90,
+ 0x18,
+ 0x10,
+ 0x10,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_trim{
+ {16, 16},
+ bitmap_icon_trim_data};
+
+static constexpr uint8_t bitmap_icon_tune_fork_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x22,
+ 0x44,
+ 0x21,
+ 0x84,
+ 0x2D,
+ 0xB4,
+ 0x25,
+ 0xA4,
+ 0x25,
+ 0xA4,
+ 0x2D,
+ 0xB4,
+ 0x61,
+ 0x86,
+ 0xC2,
+ 0x43,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_tune_fork{
+ {16, 16},
+ bitmap_icon_tune_fork_data};
+
+static constexpr uint8_t bitmap_icon_upconvert_data[] = {
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0xF0,
+ 0x0F,
+ 0xF8,
+ 0x1F,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x77,
+ 0x77,
+ 0x51,
+ 0x51,
+ 0x33,
+ 0x53,
+ 0x51,
+ 0x51,
+ 0x51,
+ 0x77,
+ 0x00,
+ 0x80,
+};
+static constexpr Bitmap bitmap_icon_upconvert{
+ {16, 16},
+ bitmap_icon_upconvert_data};
+
+static constexpr uint8_t bitmap_icon_utilities_data[] = {
+ 0x30,
+ 0x24,
+ 0x78,
+ 0x66,
+ 0x78,
+ 0x66,
+ 0x78,
+ 0x7E,
+ 0x78,
+ 0x3C,
+ 0x78,
+ 0x18,
+ 0x78,
+ 0x18,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x3C,
+ 0x30,
+ 0x18,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_utilities{
+ {16, 16},
+ bitmap_icon_utilities_data};
+
+static constexpr uint8_t bitmap_key_data[] = {
+ 0xC0,
+ 0x03,
+ 0xE0,
+ 0x07,
+ 0x30,
+ 0x0C,
+ 0x30,
+ 0x0C,
+ 0x30,
+ 0x0C,
+ 0x30,
+ 0x0C,
+ 0xE0,
+ 0x07,
+ 0xC0,
+ 0x03,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x07,
+ 0x80,
+ 0x03,
+ 0x80,
+ 0x07,
+ 0x80,
+ 0x01,
+};
+static constexpr Bitmap bitmap_key{
+ {16, 16},
+ bitmap_key_data};
+
+static constexpr uint8_t bitmap_more_data[] = {
+ 0x10,
+ 0x10,
+ 0x10,
+ 0x10,
+ 0x54,
+ 0x38,
+ 0x10,
+ 0x00,
+};
+static constexpr Bitmap bitmap_more{
+ {8, 8},
+ bitmap_more_data};
+
+static constexpr uint8_t bitmap_play_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0xFC,
+ 0x03,
+ 0xFC,
+ 0x0F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x3F,
+ 0xFC,
+ 0x0F,
+ 0xFC,
+ 0x03,
+ 0xFC,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_play{
+ {16, 16},
+ bitmap_play_data};
+
+static constexpr uint8_t bitmap_record_data[] = {
+ 0xC0,
+ 0x07,
+ 0xF0,
+ 0x1F,
+ 0xF8,
+ 0x3F,
+ 0xFC,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0x66,
+ 0xCC,
+ 0x56,
+ 0xF7,
+ 0x66,
+ 0xF6,
+ 0x56,
+ 0xF7,
+ 0x56,
+ 0xCC,
+ 0xFC,
+ 0x7F,
+ 0xFC,
+ 0x7F,
+ 0xF8,
+ 0x3F,
+ 0xF0,
+ 0x1F,
+ 0xC0,
+ 0x07,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_record{
+ {16, 16},
+ bitmap_record_data};
+
+static constexpr uint8_t bitmap_rssipwm_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x8F,
+ 0xE7,
+ 0x7D,
+ 0x51,
+ 0x10,
+ 0x10,
+ 0x51,
+ 0x10,
+ 0x10,
+ 0x8F,
+ 0xE3,
+ 0x10,
+ 0x09,
+ 0x04,
+ 0x11,
+ 0x11,
+ 0x04,
+ 0x11,
+ 0xD1,
+ 0xF3,
+ 0x7C,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3F,
+ 0x1E,
+ 0x1E,
+ 0x21,
+ 0x12,
+ 0x12,
+ 0x21,
+ 0x12,
+ 0x12,
+ 0x21,
+ 0x12,
+ 0x12,
+ 0x21,
+ 0x12,
+ 0x12,
+ 0x21,
+ 0x12,
+ 0x12,
+ 0xE1,
+ 0xF3,
+ 0x73,
+};
+static constexpr Bitmap bitmap_rssipwm{
+ {24, 16},
+ bitmap_rssipwm_data};
+
+static constexpr uint8_t bitmap_sd_card_error_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x1F,
+ 0xE0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xC8,
+ 0x13,
+ 0x98,
+ 0x19,
+ 0x38,
+ 0x1C,
+ 0x78,
+ 0x1E,
+ 0x38,
+ 0x1C,
+ 0x98,
+ 0x19,
+ 0xC8,
+ 0x13,
+ 0xF8,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sd_card_error{
+ {16, 16},
+ bitmap_sd_card_error_data};
+
+static constexpr uint8_t bitmap_sd_card_ok_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x1F,
+ 0xE0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x98,
+ 0x15,
+ 0x68,
+ 0x19,
+ 0x68,
+ 0x1D,
+ 0x68,
+ 0x19,
+ 0x98,
+ 0x15,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sd_card_ok{
+ {16, 16},
+ bitmap_sd_card_ok_data};
+
+static constexpr uint8_t bitmap_sd_card_unknown_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x1F,
+ 0xE0,
+ 0x1F,
+ 0xF0,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0x38,
+ 0x1C,
+ 0x98,
+ 0x19,
+ 0xF8,
+ 0x1C,
+ 0x78,
+ 0x1E,
+ 0x78,
+ 0x1E,
+ 0xF8,
+ 0x1F,
+ 0x78,
+ 0x1E,
+ 0xF8,
+ 0x1F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sd_card_unknown{
+ {16, 16},
+ bitmap_sd_card_unknown_data};
+
static constexpr uint8_t bitmap_sig_cw_data[] = {
0x00,
0x00,
@@ -1235,180 +4955,7 @@ static constexpr Bitmap bitmap_sig_cw{
{32, 32},
bitmap_sig_cw_data};
-static constexpr uint8_t bitmap_icon_tpms_data[] = {
- 0xC0,
- 0x03,
- 0xF0,
- 0x0F,
- 0x18,
- 0x18,
- 0xEC,
- 0x37,
- 0x36,
- 0x6D,
- 0x3A,
- 0x59,
- 0x4B,
- 0xD5,
- 0x8B,
- 0xD3,
- 0xCB,
- 0xD1,
- 0xAB,
- 0xD2,
- 0x9A,
- 0x5C,
- 0xB6,
- 0x6C,
- 0xEC,
- 0x37,
- 0x18,
- 0x18,
- 0xF0,
- 0x0F,
- 0xC0,
- 0x03,
-};
-static constexpr Bitmap bitmap_icon_tpms{
- {16, 16},
- bitmap_icon_tpms_data};
-
-static constexpr uint8_t bitmap_rssipwm_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x8F,
- 0xE7,
- 0x7D,
- 0x51,
- 0x10,
- 0x10,
- 0x51,
- 0x10,
- 0x10,
- 0x8F,
- 0xE3,
- 0x10,
- 0x09,
- 0x04,
- 0x11,
- 0x11,
- 0x04,
- 0x11,
- 0xD1,
- 0xF3,
- 0x7C,
- 0x00,
- 0x00,
- 0x00,
- 0x3F,
- 0x1E,
- 0x1E,
- 0x21,
- 0x12,
- 0x12,
- 0x21,
- 0x12,
- 0x12,
- 0x21,
- 0x12,
- 0x12,
- 0x21,
- 0x12,
- 0x12,
- 0x21,
- 0x12,
- 0x12,
- 0xE1,
- 0xF3,
- 0x73,
-};
-static constexpr Bitmap bitmap_rssipwm{
- {24, 16},
- bitmap_rssipwm_data};
-
-static constexpr uint8_t bitmap_icon_stealth_data[] = {
- 0x00,
- 0x00,
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xE0,
- 0x07,
- 0xF8,
- 0x1F,
- 0x00,
- 0x00,
- 0xE0,
- 0x07,
- 0x60,
- 0x06,
- 0x00,
- 0x00,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF8,
- 0x1E,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3E,
- 0xFC,
- 0x3F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_stealth{
- {16, 16},
- bitmap_icon_stealth_data};
-
-static constexpr uint8_t bitmap_key_data[] = {
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0x30,
- 0x0C,
- 0x30,
- 0x0C,
- 0x30,
- 0x0C,
- 0x30,
- 0x0C,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x07,
- 0x80,
- 0x03,
- 0x80,
- 0x07,
- 0x80,
- 0x01,
-};
-static constexpr Bitmap bitmap_key{
- {16, 16},
- bitmap_key_data};
-
-static constexpr uint8_t bitmap_sig_square_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
+static constexpr uint8_t bitmap_sig_noise_data[] = {
0x00,
0x00,
0x00,
@@ -1436,1890 +4983,111 @@ static constexpr uint8_t bitmap_sig_square_data[] = {
0x00,
0x00,
0x00,
- 0xFE,
- 0x83,
- 0xFF,
- 0x60,
- 0xFE,
- 0x83,
- 0xFF,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0x83,
- 0xC1,
- 0x60,
- 0x06,
- 0xFF,
- 0xC1,
- 0x7F,
- 0x06,
- 0xFF,
- 0xC1,
- 0x7F,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_sig_square{
- {32, 32},
- bitmap_sig_square_data};
-
-static constexpr uint8_t bitmap_icon_options_ui_data[] = {
- 0xFF,
- 0x1F,
- 0xFF,
- 0x13,
- 0xFF,
- 0x1F,
- 0x01,
- 0x10,
- 0x01,
- 0x10,
- 0x01,
- 0x10,
- 0x01,
- 0x04,
- 0x01,
- 0x0C,
- 0x01,
- 0x1C,
- 0x01,
- 0x3C,
- 0xFF,
- 0x7D,
- 0x00,
- 0xFC,
- 0x00,
- 0x34,
- 0x00,
0x20,
0x00,
- 0x60,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_options_ui{
- {16, 16},
- bitmap_icon_options_ui_data};
-
-static constexpr uint8_t bitmap_icon_jammer_data[] = {
- 0xE0,
- 0x07,
- 0xF8,
- 0x1F,
- 0x1C,
- 0x38,
- 0x0E,
- 0x78,
- 0x06,
- 0x7C,
+ 0x30,
+ 0x80,
+ 0x00,
+ 0x00,
+ 0x30,
+ 0x80,
+ 0x01,
+ 0x40,
+ 0x30,
+ 0xC0,
0x03,
+ 0xC0,
+ 0x30,
+ 0xC0,
+ 0x03,
+ 0xC0,
+ 0x39,
+ 0xC0,
+ 0x72,
+ 0xC0,
+ 0x7B,
+ 0x60,
+ 0x76,
+ 0x60,
+ 0x6E,
+ 0x60,
+ 0x1E,
+ 0x60,
0xCE,
- 0x03,
- 0xC7,
- 0x83,
- 0xC3,
- 0xC3,
- 0xC1,
- 0xE3,
- 0xC0,
- 0x73,
- 0xC0,
- 0x3E,
- 0x60,
- 0x1E,
- 0x70,
- 0x1C,
- 0x38,
- 0xF8,
- 0x1F,
- 0xE0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_jammer{
- {16, 16},
- bitmap_icon_jammer_data};
-
-static constexpr uint8_t bitmap_icon_qr_code_data[] = {
- 0x00,
- 0x00,
- 0xFE,
- 0x7E,
- 0xC6,
- 0x62,
- 0xFA,
- 0x5A,
- 0xFA,
- 0x5A,
- 0xDA,
- 0x5A,
- 0xFE,
- 0x7E,
- 0x7E,
- 0x7E,
- 0x00,
- 0x00,
- 0xFE,
- 0x46,
- 0xC2,
- 0x06,
- 0xFA,
- 0x18,
- 0xFA,
- 0x18,
- 0xC6,
- 0x60,
- 0xFE,
- 0x62,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_qr_code{
- {16, 16},
- bitmap_icon_qr_code_data};
-
-static constexpr uint8_t bitmap_icon_sonde_data[] = {
- 0x80,
- 0x03,
- 0xE0,
- 0x0F,
- 0xE0,
- 0x0F,
- 0xF0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xE0,
- 0x0F,
- 0xE0,
- 0x0F,
- 0x00,
- 0x00,
- 0x20,
- 0x09,
- 0x00,
- 0x00,
- 0x40,
- 0x05,
- 0x00,
- 0x00,
- 0xC0,
- 0x07,
- 0xC0,
- 0x07,
- 0xC0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_sonde{
- {16, 16},
- bitmap_icon_sonde_data};
-
-static constexpr uint8_t bitmap_icon_codetx_data[] = {
- 0x00,
- 0x00,
- 0xF0,
- 0x07,
+ 0x6C,
0x0C,
- 0x18,
- 0x03,
- 0x60,
- 0xE0,
- 0x03,
- 0x18,
- 0x0C,
- 0x04,
- 0x10,
- 0xC0,
- 0x01,
- 0x20,
- 0x02,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xBB,
- 0x6D,
- 0x2A,
- 0x49,
- 0x2A,
- 0x49,
- 0x3A,
- 0x49,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_codetx{
- {16, 16},
- bitmap_icon_codetx_data};
-
-static constexpr uint8_t bitmap_icon_scanner_data[] = {
- 0x03,
- 0x00,
- 0x00,
- 0x00,
- 0x03,
- 0x00,
- 0x00,
- 0x00,
- 0x0F,
- 0x00,
- 0x00,
- 0x00,
- 0x03,
- 0x01,
- 0x80,
- 0x01,
- 0xC3,
- 0x00,
- 0xE0,
- 0xFF,
- 0xEF,
- 0xFF,
- 0xC0,
- 0x00,
- 0x83,
- 0x01,
- 0x00,
- 0x01,
- 0x03,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_scanner{
- {16, 16},
- bitmap_icon_scanner_data};
-
-static constexpr uint8_t bitmap_icon_lge_data[] = {
- 0x00,
- 0x00,
- 0x80,
- 0x00,
- 0xA4,
- 0x12,
- 0xA8,
- 0x0A,
- 0xD0,
- 0x05,
- 0xEC,
- 0x1B,
- 0xF0,
- 0x07,
- 0xFE,
- 0xFF,
- 0xF0,
- 0x07,
- 0xEC,
- 0x1B,
- 0xD0,
- 0x05,
- 0xA8,
- 0x0A,
- 0xA4,
- 0x12,
- 0x80,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_lge{
- {16, 16},
- bitmap_icon_lge_data};
-
-static constexpr uint8_t bitmap_icon_soundboard_data[] = {
- 0xF0,
- 0x0F,
- 0x1C,
- 0x18,
- 0x17,
- 0x38,
- 0x15,
- 0x78,
- 0x15,
- 0xF8,
- 0x15,
- 0x82,
- 0x15,
- 0x8B,
- 0xD5,
- 0x83,
- 0xD5,
- 0xBB,
- 0xD5,
- 0x83,
- 0x15,
- 0x8B,
- 0x15,
- 0x92,
- 0x15,
- 0xA0,
- 0x17,
- 0x80,
- 0x1C,
- 0x80,
- 0xF0,
- 0xFF,
-};
-static constexpr Bitmap bitmap_icon_soundboard{
- {16, 16},
- bitmap_icon_soundboard_data};
-
-static constexpr uint8_t bitmap_icon_tetris_data[] = {
- 0xF8,
- 0xFF,
- 0x88,
- 0x88,
- 0x88,
- 0x88,
- 0x88,
- 0x88,
- 0xF8,
- 0xFF,
- 0x80,
- 0x08,
- 0x80,
- 0x08,
- 0x9F,
- 0x08,
- 0x91,
- 0x0F,
- 0x11,
- 0x00,
- 0x11,
- 0x00,
- 0xFF,
- 0xF1,
- 0x11,
- 0x91,
- 0x11,
- 0x91,
- 0x11,
- 0x91,
- 0xFF,
- 0xF1,
-};
-static constexpr Bitmap bitmap_icon_tetris{
- {16, 16},
- bitmap_icon_tetris_data};
-
-static constexpr uint8_t bitmap_icon_rename_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x0E,
- 0x00,
- 0x04,
- 0xFF,
- 0xF5,
- 0x01,
- 0x84,
- 0xC9,
- 0x84,
- 0x55,
- 0x85,
- 0xDD,
- 0x84,
- 0x55,
- 0x85,
- 0xD5,
- 0x84,
- 0x01,
- 0x84,
- 0xFF,
- 0xF5,
- 0x00,
- 0x04,
- 0x00,
- 0x0E,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_rename{
- {16, 16},
- bitmap_icon_rename_data};
-
-static constexpr uint8_t bitmap_icon_lora_data[] = {
- 0xC0,
- 0x03,
- 0x30,
- 0x0C,
- 0x00,
- 0x00,
- 0xC0,
- 0x03,
- 0x00,
- 0x00,
- 0xC0,
- 0x03,
- 0x60,
- 0x06,
- 0x60,
- 0x06,
- 0x60,
- 0x06,
- 0x60,
- 0x06,
- 0xC0,
- 0x03,
- 0x00,
- 0x00,
- 0xC0,
- 0x03,
- 0x00,
- 0x00,
- 0x30,
- 0x0C,
- 0xC0,
- 0x03,
-};
-static constexpr Bitmap bitmap_icon_lora{
- {16, 16},
- bitmap_icon_lora_data};
-
-static constexpr uint8_t bitmap_icon_lcr_data[] = {
- 0x0C,
- 0x00,
- 0xFF,
- 0x7F,
- 0x01,
- 0x80,
- 0xC1,
- 0x9B,
- 0xFF,
- 0x7F,
- 0x0C,
- 0x00,
- 0xFF,
- 0x7F,
- 0x01,
- 0x80,
- 0xC1,
- 0x9D,
- 0xFF,
- 0x7F,
- 0x0C,
- 0x00,
- 0x0C,
- 0x00,
- 0x0C,
- 0x00,
- 0x0C,
- 0x00,
- 0x0C,
- 0x00,
- 0x0C,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_lcr{
- {16, 16},
- bitmap_icon_lcr_data};
-
-static constexpr uint8_t bitmap_icon_file_data[] = {
- 0xFC,
- 0x03,
- 0x04,
- 0x06,
- 0x04,
- 0x0E,
- 0x04,
- 0x1E,
- 0x04,
- 0x3E,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0xFC,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_file{
- {16, 16},
- bitmap_icon_file_data};
-
-static constexpr uint8_t bitmap_icon_clk_ext_data[] = {
- 0x00,
- 0x00,
- 0xDC,
- 0x54,
- 0x54,
- 0x54,
- 0x54,
- 0x76,
- 0x00,
- 0x10,
- 0x38,
- 0x7C,
- 0x10,
- 0x10,
- 0x10,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_clk_ext{
- {8, 16},
- bitmap_icon_clk_ext_data};
-
-static constexpr uint8_t bitmap_icon_trash_data[] = {
- 0x00,
- 0x00,
- 0xC0,
- 0x01,
- 0x20,
- 0x02,
- 0xFC,
- 0x1F,
- 0x00,
- 0x00,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0xA8,
- 0x0A,
- 0x08,
- 0x08,
- 0xF0,
- 0x07,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_trash{
- {16, 16},
- bitmap_icon_trash_data};
-
-static constexpr uint8_t bitmap_icon_fox_data[] = {
- 0x18,
- 0x18,
- 0x28,
- 0x14,
- 0x68,
- 0x16,
- 0x68,
- 0x16,
- 0xC8,
- 0x13,
- 0x88,
- 0x11,
- 0x04,
- 0x20,
- 0x24,
- 0x24,
- 0x22,
- 0x44,
- 0x01,
- 0x80,
- 0x06,
- 0x60,
- 0x98,
- 0x19,
- 0x20,
- 0x04,
- 0x40,
- 0x02,
- 0x80,
- 0x01,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_fox{
- {16, 16},
- bitmap_icon_fox_data};
-
-static constexpr uint8_t bitmap_icon_script_data[] = {
- 0xFC,
- 0x07,
- 0xFA,
- 0x0F,
- 0x19,
- 0x1A,
- 0xF9,
- 0x1F,
- 0x1E,
- 0x1D,
- 0xF8,
- 0x1F,
- 0x98,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x08,
- 0x14,
- 0xF8,
- 0x1F,
- 0x48,
- 0x1E,
- 0xF8,
- 0xFF,
- 0x78,
- 0x80,
- 0x30,
- 0x40,
- 0xE0,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_script{
- {16, 16},
- bitmap_icon_script_data};
-
-static constexpr uint8_t bitmap_icon_hackrf_data[] = {
- 0xF0,
- 0x0F,
- 0x10,
- 0x08,
- 0x50,
- 0x0A,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF0,
- 0x0F,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
-};
-static constexpr Bitmap bitmap_icon_hackrf{
- {16, 16},
- bitmap_icon_hackrf_data};
-
-static constexpr uint8_t bitmap_icon_dmr_data[] = {
- 0x00,
- 0x00,
- 0xFE,
- 0x1F,
- 0xFE,
- 0x3F,
- 0x0E,
- 0x78,
- 0x0E,
- 0x70,
- 0x0E,
- 0x70,
- 0x0E,
- 0x70,
- 0x0E,
- 0x78,
- 0xFE,
- 0x3F,
- 0xFE,
- 0x1F,
- 0x8E,
- 0x07,
- 0x0E,
- 0x0F,
- 0x0E,
- 0x1E,
- 0x0E,
- 0x3C,
- 0x0E,
- 0x78,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_dmr{
- {16, 16},
- bitmap_icon_dmr_data};
-
-static constexpr uint8_t bitmap_arrow_right_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x04,
- 0x00,
- 0x0C,
- 0x00,
- 0x1C,
- 0xFE,
- 0x3F,
- 0xFE,
- 0x7F,
- 0xFE,
- 0x3F,
- 0x00,
- 0x1C,
- 0x00,
- 0x0C,
- 0x00,
- 0x04,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_arrow_right{
- {16, 16},
- bitmap_arrow_right_data};
-
-static constexpr uint8_t bitmap_icon_upconvert_data[] = {
- 0x80,
- 0x01,
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xF0,
- 0x0F,
- 0xF8,
- 0x1F,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x00,
- 0x00,
- 0x77,
- 0x77,
- 0x51,
- 0x51,
- 0x33,
- 0x53,
- 0x51,
- 0x51,
- 0x51,
- 0x77,
- 0x00,
- 0x80,
-};
-static constexpr Bitmap bitmap_icon_upconvert{
- {16, 16},
- bitmap_icon_upconvert_data};
-
-static constexpr uint8_t bitmap_icon_hide_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x40,
- 0x00,
- 0x20,
- 0xE0,
- 0x17,
- 0x18,
- 0x18,
+ 0x66,
0xC4,
- 0x27,
- 0x62,
- 0x42,
- 0x21,
- 0x85,
- 0xA1,
- 0x84,
- 0x62,
- 0x46,
- 0xA4,
- 0x23,
- 0x18,
- 0x18,
- 0xE8,
- 0x07,
- 0x04,
- 0x00,
- 0x02,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_hide{
- {16, 16},
- bitmap_icon_hide_data};
-
-static constexpr uint8_t bitmap_icon_add_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_add{
- {16, 16},
- bitmap_icon_add_data};
-
-static constexpr uint8_t bitmap_icon_clk_int_data[] = {
- 0x00,
- 0x00,
- 0xDC,
- 0x54,
- 0x54,
- 0x54,
- 0x54,
- 0x76,
- 0x00,
- 0x44,
- 0x6C,
- 0x38,
- 0x38,
- 0x6C,
- 0x44,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_clk_int{
- {8, 16},
- bitmap_icon_clk_int_data};
-
-static constexpr uint8_t bitmap_icon_rds_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x3C,
- 0x3C,
- 0x7E,
- 0x7E,
- 0x67,
- 0xE7,
- 0x83,
- 0xC3,
- 0xC7,
- 0xE1,
- 0xFD,
- 0xBC,
- 0x42,
- 0x42,
- 0x3C,
- 0x3C,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_rds{
- {16, 16},
- bitmap_icon_rds_data};
-
-static constexpr uint8_t bitmap_icon_file_image_data[] = {
- 0x00,
- 0x00,
- 0xFF,
- 0xFF,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x89,
- 0x80,
- 0xC1,
- 0x81,
- 0xE1,
- 0xA3,
- 0xB1,
- 0xB3,
- 0x89,
- 0xDC,
- 0x07,
- 0x8C,
- 0x01,
- 0x90,
- 0x01,
- 0x80,
- 0xAB,
- 0x82,
- 0xFF,
- 0xD5,
- 0xFF,
- 0xFF,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_file_image{
- {16, 16},
- bitmap_icon_file_image_data};
-
-static constexpr uint8_t bitmap_icon_batt_icon_data[] = {
- 0xC0,
- 0x03,
- 0xC0,
- 0x03,
- 0xF0,
- 0x0F,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0x10,
- 0x08,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_batt_icon{
- {16, 16},
- bitmap_icon_batt_icon_data};
-
-static constexpr uint8_t bitmap_tab_edge_data[] = {
- 0x00,
- 0x01,
- 0x01,
- 0x03,
- 0x03,
- 0x03,
- 0x07,
- 0x07,
- 0x07,
- 0x0F,
- 0x0F,
- 0x0F,
- 0x1F,
- 0x1F,
- 0x1F,
- 0x1F,
- 0x3F,
- 0x3F,
- 0x3F,
- 0x7F,
- 0x7F,
- 0x7F,
- 0xFF,
- 0xFF,
-};
-static constexpr Bitmap bitmap_tab_edge{
- {8, 24},
- bitmap_tab_edge_data};
-
-static constexpr uint8_t bitmap_icon_sdcard_data[] = {
- 0xF0,
- 0x3F,
- 0x58,
- 0x35,
- 0x5C,
- 0x35,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xAC,
- 0x3A,
- 0x5C,
- 0x35,
- 0xAC,
- 0x3A,
- 0x5C,
- 0x35,
- 0xAC,
- 0x3A,
- 0x5C,
- 0x35,
- 0xAC,
- 0x3A,
-};
-static constexpr Bitmap bitmap_icon_sdcard{
- {16, 16},
- bitmap_icon_sdcard_data};
-
-static constexpr uint8_t bitmap_icon_clean_data[] = {
- 0x00,
- 0x00,
- 0xC0,
- 0x01,
- 0x20,
- 0x02,
- 0xFC,
- 0x1F,
- 0x00,
- 0x00,
- 0x08,
- 0x08,
- 0xE8,
- 0x08,
- 0xA8,
- 0x09,
- 0xA8,
- 0x0B,
- 0x28,
- 0x0A,
- 0x28,
- 0x0A,
- 0x28,
- 0x0A,
- 0xE8,
- 0x0B,
- 0x08,
- 0x08,
- 0xF0,
- 0x07,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_clean{
- {16, 16},
- bitmap_icon_clean_data};
-
-static constexpr uint8_t bitmap_icon_touchtunes_data[] = {
- 0xE0,
- 0x07,
- 0x30,
+ 0x6E,
0x0C,
- 0x7C,
- 0x3E,
- 0xC4,
- 0x23,
- 0x26,
- 0x64,
- 0x12,
- 0x48,
- 0x0F,
- 0xF3,
- 0x09,
- 0x95,
- 0x0F,
- 0xF1,
- 0x09,
- 0x91,
- 0x0F,
- 0xF1,
- 0xC9,
- 0x91,
- 0xE9,
- 0x91,
- 0xC9,
- 0x90,
- 0x0F,
- 0xF0,
- 0xFF,
- 0xFF,
-};
-static constexpr Bitmap bitmap_icon_touchtunes{
- {16, 16},
- bitmap_icon_touchtunes_data};
-
-static constexpr uint8_t bitmap_icon_file_iq_data[] = {
- 0xFC,
- 0x03,
- 0x04,
- 0x06,
- 0x04,
- 0x0E,
- 0x04,
- 0x1E,
- 0x04,
- 0x3E,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0x04,
- 0x21,
- 0x44,
- 0x25,
- 0x54,
- 0x25,
- 0xF4,
- 0x2F,
- 0xA4,
- 0x2A,
- 0x84,
- 0x22,
- 0x04,
- 0x22,
- 0x04,
- 0x20,
- 0xFC,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_file_iq{
- {16, 16},
- bitmap_icon_file_iq_data};
-
-static constexpr uint8_t bitmap_icon_sd_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
+ 0x66,
0xC0,
- 0x1F,
- 0xE0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x98,
- 0x18,
- 0xE8,
- 0x16,
- 0xC8,
- 0x16,
- 0x98,
- 0x16,
- 0xB8,
- 0x16,
- 0xC8,
- 0x18,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_sd{
- {16, 16},
- bitmap_icon_sd_data};
-
-static constexpr uint8_t bitmap_icon_camera_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xE0,
- 0x07,
- 0xF0,
- 0x0F,
0x3E,
- 0x7C,
- 0xDE,
- 0x7B,
- 0xEE,
- 0x77,
- 0xEE,
- 0x77,
- 0xEE,
- 0x77,
- 0xEE,
- 0x77,
- 0xDE,
- 0x7B,
- 0x3E,
- 0x7C,
- 0xFE,
- 0x7F,
0x00,
+ 0x2C,
+ 0xC0,
+ 0x3B,
0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_camera{
- {16, 16},
- bitmap_icon_camera_data};
-
-static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = {
- 0xF0,
- 0x3F,
- 0x58,
- 0x35,
- 0x5C,
- 0x35,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
0x3C,
- 0x1C,
- 0xBC,
- 0xC9,
- 0xBC,
- 0xE3,
- 0x2C,
- 0x77,
- 0x5C,
- 0x3E,
- 0xAC,
- 0x1C,
- 0x5C,
- 0x3E,
- 0x2C,
- 0x77,
- 0x9C,
- 0xE3,
- 0xAC,
- 0xC1,
-};
-static constexpr Bitmap bitmap_icon_tools_wipesd{
- {16, 16},
- bitmap_icon_tools_wipesd_data};
-
-static constexpr uint8_t bitmap_icon_options_radio_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x04,
- 0x20,
- 0x12,
- 0x48,
- 0x8A,
- 0x51,
- 0xCA,
- 0x53,
- 0xCA,
- 0x53,
- 0x8A,
- 0x51,
- 0x12,
- 0x48,
- 0x84,
- 0x21,
0xC0,
- 0x03,
+ 0x39,
+ 0x00,
+ 0x3C,
+ 0xC0,
+ 0x18,
+ 0x00,
+ 0x18,
0x40,
- 0x02,
- 0x60,
- 0x06,
- 0x20,
- 0x04,
- 0x30,
- 0x0C,
- 0xF0,
- 0x0F,
-};
-static constexpr Bitmap bitmap_icon_options_radio{
- {16, 16},
- bitmap_icon_options_radio_data};
-
-static constexpr uint8_t bitmap_icon_freqman_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x7E,
- 0x7E,
- 0x81,
- 0x81,
- 0xBD,
- 0xBD,
- 0x81,
- 0x81,
- 0xBD,
- 0xBD,
- 0x81,
- 0x81,
- 0xBD,
- 0x9D,
- 0x81,
- 0x81,
- 0xBD,
- 0xE1,
- 0x81,
- 0x61,
- 0x7E,
- 0x3E,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_freqman{
- {16, 16},
- bitmap_icon_freqman_data};
-
-static constexpr uint8_t bitmap_icon_thermometer_data[] = {
- 0xC0,
- 0x00,
- 0x20,
- 0x01,
0x10,
- 0x02,
+ 0x00,
+ 0x18,
+ 0x40,
0x10,
- 0x3A,
- 0x10,
- 0x02,
- 0x10,
- 0x1A,
- 0x10,
- 0x02,
- 0xD0,
- 0x3A,
- 0xD0,
- 0x02,
- 0xD0,
- 0x1A,
- 0xD0,
- 0x02,
- 0xE8,
- 0x05,
- 0xE8,
- 0x05,
- 0xC8,
- 0x04,
- 0x10,
- 0x02,
- 0xE0,
- 0x01,
-};
-static constexpr Bitmap bitmap_icon_thermometer{
- {16, 16},
- bitmap_icon_thermometer_data};
-
-static constexpr uint8_t bitmap_sd_card_ok_data[] = {
0x00,
0x00,
0x00,
0x00,
- 0xC0,
- 0x1F,
- 0xE0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x98,
- 0x15,
- 0x68,
- 0x19,
- 0x68,
- 0x1D,
- 0x68,
- 0x19,
- 0x98,
- 0x15,
- 0xF8,
- 0x1F,
- 0xF8,
- 0x1F,
0x00,
0x00,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_sd_card_ok{
- {16, 16},
- bitmap_sd_card_ok_data};
-
-static constexpr uint8_t bitmap_icon_dir_data[] = {
- 0x00,
- 0x00,
- 0x3E,
- 0x00,
- 0x41,
- 0x00,
- 0xC1,
- 0x7F,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xAF,
- 0xEA,
- 0x57,
- 0xF5,
- 0xEF,
- 0xEF,
- 0xF7,
- 0xF7,
- 0xEE,
- 0x6F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_dir{
- {16, 16},
- bitmap_icon_dir_data};
-
-static constexpr uint8_t bitmap_icon_memory_data[] = {
- 0x54,
- 0x15,
- 0x54,
- 0x15,
- 0xFF,
- 0x7F,
- 0xFC,
- 0x1F,
- 0xFF,
- 0x7F,
- 0xCC,
- 0x19,
- 0xAF,
- 0x7A,
- 0x6C,
- 0x1B,
- 0xEF,
- 0x7B,
- 0xEC,
- 0x1B,
- 0xFF,
- 0x7F,
- 0xFC,
- 0x1F,
- 0xFF,
- 0x7F,
- 0x54,
- 0x15,
- 0x54,
- 0x15,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_memory{
- {16, 16},
- bitmap_icon_memory_data};
-
-static constexpr uint8_t bitmap_icon_remote_data[] = {
- 0x20,
- 0x00,
- 0x20,
- 0x00,
- 0x20,
- 0x00,
- 0x20,
- 0x00,
- 0xE0,
- 0x07,
- 0xF0,
- 0x0F,
- 0x30,
- 0x0C,
- 0x30,
- 0x0C,
- 0xF0,
- 0x0F,
- 0xF0,
- 0x0F,
- 0x70,
- 0x0D,
- 0xB0,
- 0x0E,
- 0x70,
- 0x0D,
- 0xB0,
- 0x0E,
- 0xF0,
- 0x0F,
- 0xE0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_remote{
- {16, 16},
- bitmap_icon_remote_data};
-
-static constexpr uint8_t bitmap_icon_save_data[] = {
- 0xFC,
- 0x07,
- 0x0A,
- 0x0A,
- 0x0A,
- 0x12,
- 0xF2,
- 0x21,
- 0x02,
- 0x20,
- 0x02,
- 0x20,
- 0x02,
- 0x20,
- 0x02,
- 0x20,
- 0xFA,
- 0x27,
- 0xFA,
- 0x2F,
- 0x0A,
- 0x28,
- 0xFA,
- 0x2F,
- 0x0A,
- 0x28,
- 0xFA,
- 0x2F,
- 0xFC,
- 0x1F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_save{
- {16, 16},
- bitmap_icon_save_data};
-
-static constexpr uint8_t bitmap_icon_back_data[] = {
- 0x00,
- 0x00,
- 0x30,
- 0x00,
- 0x38,
- 0x00,
- 0x1C,
- 0x00,
- 0x0E,
- 0x00,
- 0xFF,
- 0x3F,
- 0xFF,
- 0x7F,
- 0x0E,
- 0xE0,
- 0x1C,
- 0xC0,
- 0x38,
- 0xC0,
- 0x30,
- 0xC0,
- 0x00,
- 0xE0,
- 0x00,
- 0x7F,
- 0x00,
- 0x3F,
0x00,
0x00,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_back{
- {16, 16},
- bitmap_icon_back_data};
-
-static constexpr uint8_t bitmap_icon_bht_data[] = {
- 0x00,
- 0x00,
- 0xE0,
- 0x07,
- 0xF8,
- 0x08,
- 0x9C,
- 0x07,
- 0x0C,
- 0x00,
- 0x8E,
- 0x0A,
- 0x46,
- 0x12,
- 0x26,
- 0x22,
- 0x06,
- 0x02,
- 0x06,
- 0x00,
- 0x06,
- 0x00,
- 0x06,
- 0x00,
- 0x06,
- 0x00,
- 0x06,
- 0x00,
- 0x06,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_bht{
- {16, 16},
- bitmap_icon_bht_data};
-
-static constexpr uint8_t bitmap_icon_shift_data[] = {
- 0x00,
- 0x00,
- 0x80,
- 0x00,
- 0xC0,
- 0x01,
- 0xE0,
- 0x03,
- 0xF0,
- 0x07,
- 0xF8,
- 0x0F,
- 0xFC,
- 0x1F,
- 0xE0,
- 0x03,
- 0xE0,
- 0x03,
- 0xE0,
- 0x03,
- 0x20,
- 0x02,
- 0xE0,
- 0x03,
- 0x20,
- 0x02,
- 0xE0,
- 0x03,
0x00,
0x00,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_shift{
- {16, 16},
- bitmap_icon_shift_data};
-
-static constexpr uint8_t bitmap_play_data[] = {
0x00,
0x00,
0x00,
0x00,
- 0x0C,
0x00,
- 0x3C,
0x00,
- 0xFC,
0x00,
- 0xFC,
- 0x03,
- 0xFC,
- 0x0F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x0F,
- 0xFC,
- 0x03,
- 0xFC,
0x00,
- 0x3C,
0x00,
- 0x0C,
+ 0x00,
+ 0x00,
+ 0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
-static constexpr Bitmap bitmap_play{
- {16, 16},
- bitmap_play_data};
-
-static constexpr uint8_t bitmap_icon_setup_data[] = {
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xE4,
- 0x13,
- 0xFE,
- 0x3F,
- 0xFF,
- 0x7F,
- 0x3E,
- 0x3E,
- 0x1C,
- 0x1C,
- 0x1C,
- 0x1C,
- 0x1C,
- 0x1C,
- 0x3E,
- 0x3E,
- 0xFF,
- 0x7F,
- 0xFE,
- 0x3F,
- 0xE4,
- 0x13,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_setup{
- {16, 16},
- bitmap_icon_setup_data};
+static constexpr Bitmap bitmap_sig_noise{
+ {32, 32},
+ bitmap_sig_noise_data};
static constexpr uint8_t bitmap_sig_saw_down_data[] = {
0x00,
@@ -3455,463 +5223,715 @@ static constexpr Bitmap bitmap_sig_saw_down{
{32, 32},
bitmap_sig_saw_down_data};
-static constexpr uint8_t bitmap_icon_font_viewer_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x10,
- 0x0C,
- 0x38,
- 0x0C,
- 0x38,
- 0x0C,
- 0x6C,
- 0x0C,
- 0x6C,
- 0x0C,
- 0xC6,
- 0x7C,
- 0xFE,
- 0xFC,
- 0xFF,
- 0x8D,
- 0x83,
- 0x8D,
- 0x83,
- 0xFD,
- 0x01,
- 0x7D,
+static constexpr uint8_t bitmap_sig_saw_up_data[] = {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_font_viewer{
- {16, 16},
- bitmap_icon_font_viewer_data};
-
-static constexpr uint8_t bitmap_icon_load_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x06,
0x00,
0x01,
- 0x80,
- 0x03,
- 0x40,
- 0x05,
- 0x00,
- 0x01,
- 0x0E,
- 0x01,
- 0x11,
- 0x01,
- 0x7F,
- 0x1D,
- 0x01,
- 0x20,
- 0xF9,
- 0xFF,
- 0xF9,
- 0xFF,
- 0xFD,
- 0x7F,
- 0xFD,
- 0x7F,
- 0xFF,
- 0x3F,
- 0xFF,
- 0x3F,
- 0xFF,
- 0x1F,
- 0xFF,
- 0x1F,
-};
-static constexpr Bitmap bitmap_icon_load{
- {16, 16},
- bitmap_icon_load_data};
-
-static constexpr uint8_t bitmap_icon_speaker_mute_data[] = {
- 0x00,
- 0x00,
- 0x40,
- 0x00,
- 0x60,
- 0x00,
0x70,
- 0x00,
- 0x78,
- 0x00,
- 0x7F,
- 0x22,
- 0x7F,
- 0x36,
- 0x7F,
- 0x1C,
- 0x7F,
- 0x08,
- 0x7F,
- 0x1C,
- 0x7F,
- 0x36,
- 0x7F,
- 0x22,
- 0x78,
- 0x00,
- 0x70,
- 0x00,
- 0x60,
- 0x00,
- 0x40,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_speaker_mute{
- {16, 16},
- bitmap_icon_speaker_mute_data};
-
-static constexpr uint8_t bitmap_icon_modem_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xF8,
- 0x1F,
- 0x04,
- 0x20,
- 0x02,
- 0x40,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xAB,
- 0xDF,
- 0xAB,
- 0xDF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_modem{
- {16, 16},
- bitmap_icon_modem_data};
-
-static constexpr uint8_t bitmap_bulb_off_data[] = {
- 0x00,
- 0x3C,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x80,
- 0x00,
- 0x01,
- 0x40,
- 0x00,
- 0x02,
- 0x20,
- 0x00,
- 0x04,
- 0x20,
- 0x00,
- 0x04,
- 0x10,
- 0x00,
- 0x08,
- 0x10,
- 0x42,
- 0x08,
- 0x10,
- 0x42,
- 0x08,
- 0x10,
- 0x24,
- 0x08,
- 0x10,
- 0x24,
- 0x08,
- 0x20,
- 0x24,
- 0x04,
- 0x20,
- 0x2C,
- 0x04,
- 0x40,
- 0x34,
- 0x02,
- 0x80,
- 0x3C,
- 0x01,
- 0x00,
- 0xFF,
- 0x00,
- 0x00,
- 0xE3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0x42,
- 0x00,
- 0x00,
- 0x3C,
- 0x00,
-};
-static constexpr Bitmap bitmap_bulb_off{
- {24, 24},
- bitmap_bulb_off_data};
-
-static constexpr uint8_t bitmap_icon_sleep_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x04,
- 0x00,
- 0x08,
- 0x00,
- 0x18,
- 0x00,
- 0x18,
- 0x00,
- 0x38,
- 0x00,
- 0x3C,
- 0x00,
- 0x3C,
- 0x00,
- 0x3E,
- 0x84,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xF0,
- 0x0F,
- 0xC0,
- 0x03,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_sleep{
- {16, 16},
- bitmap_icon_sleep_data};
-
-static constexpr uint8_t bitmap_icon_paint_data[] = {
- 0xFE,
- 0x3F,
- 0xFF,
- 0x3F,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xBF,
- 0xFE,
- 0xBF,
- 0x00,
- 0x80,
- 0x80,
- 0xFF,
- 0x80,
- 0x00,
- 0x80,
- 0x00,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x01,
-};
-static constexpr Bitmap bitmap_icon_paint{
- {16, 16},
- bitmap_icon_paint_data};
-
-static constexpr uint8_t bitmap_icon_notepad_data[] = {
- 0x0C,
- 0x00,
- 0x1E,
- 0x00,
- 0x2F,
- 0x00,
- 0x47,
- 0x00,
- 0xE2,
- 0x00,
- 0xD4,
- 0x01,
- 0xB8,
- 0x03,
- 0x70,
- 0x07,
- 0xE0,
- 0x0E,
- 0xC0,
- 0x1D,
- 0x80,
- 0x3B,
- 0x00,
- 0x4F,
- 0x00,
- 0x46,
- 0x00,
- 0x84,
- 0x00,
- 0xD8,
- 0x00,
- 0xE0,
-};
-static constexpr Bitmap bitmap_icon_notepad{
- {16, 16},
- bitmap_icon_notepad_data};
-
-static constexpr uint8_t bitmap_icon_trim_data[] = {
- 0x10,
- 0x10,
- 0x30,
- 0x12,
- 0x50,
- 0x15,
- 0x50,
- 0x15,
- 0x50,
- 0x15,
- 0x52,
- 0x95,
- 0x56,
- 0xD5,
- 0x5F,
- 0xF5,
- 0x56,
- 0xD5,
- 0x52,
- 0x95,
- 0x50,
- 0x15,
- 0x50,
- 0x15,
- 0x50,
- 0x15,
- 0x90,
- 0x18,
- 0x10,
- 0x10,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_trim{
- {16, 16},
- bitmap_icon_trim_data};
-
-static constexpr uint8_t bitmap_icon_copy_data[] = {
- 0x00,
- 0x00,
- 0xFC,
- 0x00,
- 0x84,
- 0x01,
- 0xC4,
- 0x0F,
- 0x74,
- 0x18,
- 0x44,
- 0x38,
- 0x44,
- 0x78,
- 0x74,
- 0x40,
- 0x44,
- 0x44,
- 0x44,
- 0x44,
- 0x74,
- 0x5F,
- 0x44,
- 0x44,
- 0x44,
- 0x44,
- 0x7C,
- 0x40,
- 0xC0,
- 0x7F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_copy{
- {16, 16},
- bitmap_icon_copy_data};
-
-static constexpr uint8_t bitmap_icon_cut_data[] = {
- 0x00,
- 0x00,
- 0x10,
- 0x10,
- 0x30,
- 0x18,
- 0x20,
- 0x08,
- 0x60,
- 0x0C,
- 0x40,
- 0x04,
- 0xC0,
0x06,
0x80,
+ 0x01,
+ 0x78,
+ 0x06,
+ 0xC0,
+ 0x01,
+ 0x7C,
+ 0x06,
+ 0xE0,
+ 0x01,
+ 0x6E,
+ 0x06,
+ 0xF0,
+ 0x01,
+ 0x67,
+ 0x06,
+ 0xB8,
+ 0x81,
+ 0x63,
+ 0x06,
+ 0x9C,
+ 0xC1,
+ 0x61,
+ 0x06,
+ 0x8E,
+ 0xE1,
+ 0x60,
+ 0x06,
+ 0x87,
+ 0x71,
+ 0x60,
+ 0x86,
+ 0x83,
+ 0x39,
+ 0x60,
+ 0xC6,
+ 0x81,
+ 0x1D,
+ 0x60,
+ 0xE6,
+ 0x80,
+ 0x0F,
+ 0x60,
+ 0x76,
+ 0x80,
+ 0x07,
+ 0x60,
+ 0x3E,
+ 0x80,
+ 0x03,
+ 0x60,
+ 0x1E,
+ 0x80,
+ 0x01,
+ 0x60,
+ 0x0E,
+ 0x80,
+ 0x00,
+ 0x60,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sig_saw_up{
+ {32, 32},
+ bitmap_sig_saw_up_data};
+
+static constexpr uint8_t bitmap_sig_sine_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0xC0,
0x00,
0x80,
- 0x01,
- 0x80,
+ 0x07,
+ 0xE0,
0x01,
0xC0,
+ 0x0F,
+ 0xF0,
0x03,
- 0x78,
+ 0xC0,
+ 0x0C,
+ 0x30,
+ 0x03,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x18,
+ 0x60,
+ 0x06,
+ 0x18,
0x1E,
- 0x44,
- 0x22,
- 0x44,
- 0x22,
- 0x44,
- 0x22,
- 0x38,
- 0x1C,
+ 0xE0,
+ 0x07,
+ 0x78,
+ 0x0E,
+ 0xC0,
+ 0x03,
+ 0x70,
+ 0x06,
+ 0x80,
+ 0x01,
+ 0x60,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
};
-static constexpr Bitmap bitmap_icon_cut{
+static constexpr Bitmap bitmap_sig_sine{
+ {32, 32},
+ bitmap_sig_sine_data};
+
+static constexpr uint8_t bitmap_sig_square_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFE,
+ 0x83,
+ 0xFF,
+ 0x60,
+ 0xFE,
+ 0x83,
+ 0xFF,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0x83,
+ 0xC1,
+ 0x60,
+ 0x06,
+ 0xFF,
+ 0xC1,
+ 0x7F,
+ 0x06,
+ 0xFF,
+ 0xC1,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sig_square{
+ {32, 32},
+ bitmap_sig_square_data};
+
+static constexpr uint8_t bitmap_sig_tri_data[] = {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0xC0,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0xC0,
+ 0x00,
+ 0x80,
+ 0x07,
+ 0xE0,
+ 0x01,
+ 0x80,
+ 0x07,
+ 0xE0,
+ 0x01,
+ 0xC0,
+ 0x0C,
+ 0x30,
+ 0x03,
+ 0xC0,
+ 0x0C,
+ 0x30,
+ 0x03,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x60,
+ 0x18,
+ 0x18,
+ 0x06,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x30,
+ 0x30,
+ 0x0C,
+ 0x0C,
+ 0x18,
+ 0x60,
+ 0x06,
+ 0x18,
+ 0x18,
+ 0x60,
+ 0x06,
+ 0x18,
+ 0x0E,
+ 0xC0,
+ 0x03,
+ 0x70,
+ 0x0E,
+ 0xC0,
+ 0x03,
+ 0x70,
+ 0x06,
+ 0x80,
+ 0x01,
+ 0x60,
+ 0x06,
+ 0x80,
+ 0x01,
+ 0x60,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_sig_tri{
+ {32, 32},
+ bitmap_sig_tri_data};
+
+static constexpr uint8_t bitmap_stop_data[] = {
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x8B,
+ 0xCD,
+ 0xDD,
+ 0xAA,
+ 0xDB,
+ 0xCA,
+ 0xDB,
+ 0xEA,
+ 0xDD,
+ 0xED,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_stop{
{16, 16},
- bitmap_icon_cut_data};
+ bitmap_stop_data};
+
+static constexpr uint8_t bitmap_stopwatch_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,
+};
+static constexpr Bitmap bitmap_stopwatch{
+ {16, 16},
+ bitmap_stopwatch_data};
+
+static constexpr uint8_t bitmap_stripes_data[] = {
+ 0xFF,
+ 0x03,
+ 0xC0,
+ 0xFF,
+ 0x01,
+ 0xE0,
+ 0xFF,
+ 0x00,
+ 0xF0,
+ 0x7F,
+ 0x00,
+ 0xF8,
+ 0x3F,
+ 0x00,
+ 0xFC,
+ 0x1F,
+ 0x00,
+ 0xFE,
+ 0x0F,
+ 0x00,
+ 0xFF,
+ 0x07,
+ 0x80,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_stripes{
+ {24, 8},
+ bitmap_stripes_data};
+
+static constexpr uint8_t bitmap_tab_edge_data[] = {
+ 0x00,
+ 0x01,
+ 0x01,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x07,
+ 0x07,
+ 0x07,
+ 0x0F,
+ 0x0F,
+ 0x0F,
+ 0x1F,
+ 0x1F,
+ 0x1F,
+ 0x1F,
+ 0x3F,
+ 0x3F,
+ 0x3F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+};
+static constexpr Bitmap bitmap_tab_edge{
+ {8, 24},
+ bitmap_tab_edge_data};
+
+static constexpr uint8_t bitmap_target_data[] = {
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0xE0,
+ 0x03,
+ 0x90,
+ 0x04,
+ 0x88,
+ 0x08,
+ 0x04,
+ 0x10,
+ 0x04,
+ 0x10,
+ 0x1F,
+ 0x7C,
+ 0x04,
+ 0x10,
+ 0x04,
+ 0x10,
+ 0x88,
+ 0x08,
+ 0x90,
+ 0x04,
+ 0xE0,
+ 0x03,
+ 0x80,
+ 0x00,
+ 0x80,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_target{
+ {16, 16},
+ bitmap_target_data};
static constexpr uint8_t bitmap_target_calibrate_data[] = {
0x02,
@@ -4047,747 +6067,177 @@ static constexpr Bitmap bitmap_target_calibrate{
{32, 32},
bitmap_target_calibrate_data};
-static constexpr uint8_t bitmap_icon_controls_data[] = {
- 0x8C,
- 0x31,
- 0x5A,
- 0x6B,
- 0xDE,
- 0x7B,
- 0x8C,
- 0x31,
+static constexpr uint8_t bitmap_target_verify_data[] = {
0x00,
- 0x00,
- 0x8C,
- 0x31,
- 0x5A,
- 0x7B,
- 0xDE,
- 0x7B,
- 0x8C,
- 0x31,
- 0x00,
- 0x00,
- 0x8C,
- 0x31,
- 0xDA,
- 0x7B,
- 0xDE,
- 0x7B,
- 0x8C,
- 0x31,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_controls{
- {16, 16},
- bitmap_icon_controls_data};
-
-static constexpr uint8_t bitmap_icon_pocsag_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xFC,
- 0x3F,
- 0xFE,
- 0x7F,
- 0x02,
- 0x40,
- 0xBA,
- 0x45,
- 0x02,
- 0x40,
- 0xFE,
- 0x7F,
- 0xFE,
- 0x7F,
- 0x92,
- 0x7C,
- 0x92,
- 0x7C,
- 0xFC,
- 0x3F,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_pocsag{
- {16, 16},
- bitmap_icon_pocsag_data};
-
-static constexpr uint8_t bitmap_stop_data[] = {
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0x8B,
- 0xCD,
- 0xDD,
- 0xAA,
- 0xDB,
- 0xCA,
- 0xDB,
- 0xEA,
- 0xDD,
- 0xED,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_stop{
- {16, 16},
- bitmap_stop_data};
-
-static constexpr uint8_t bitmap_icon_speaker_data[] = {
- 0x00,
- 0x00,
- 0x40,
- 0x10,
- 0x60,
- 0x20,
- 0x70,
- 0x44,
- 0x78,
- 0x48,
- 0x7F,
- 0x91,
- 0x7F,
- 0x92,
- 0x7F,
- 0x92,
- 0x7F,
- 0x92,
- 0x7F,
- 0x92,
- 0x7F,
- 0x92,
- 0x7F,
- 0x91,
- 0x78,
- 0x48,
- 0x70,
- 0x44,
- 0x60,
- 0x20,
- 0x40,
- 0x10,
-};
-static constexpr Bitmap bitmap_icon_speaker{
- {16, 16},
- bitmap_icon_speaker_data};
-
-static constexpr uint8_t bitmap_icon_replay_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x0C,
- 0x00,
- 0x3C,
- 0x00,
- 0xFC,
- 0x00,
- 0xFC,
- 0x03,
- 0xFC,
- 0x0F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x3F,
- 0xFC,
- 0x0F,
- 0xFC,
- 0x03,
- 0xFC,
- 0x00,
- 0x3C,
- 0x00,
- 0x0C,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_replay{
- {16, 16},
- bitmap_icon_replay_data};
-
-static constexpr uint8_t bitmap_icon_rename_numeric_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x0E,
- 0x00,
- 0x04,
- 0xFF,
- 0xF5,
- 0x01,
- 0x84,
- 0xC9,
- 0x85,
- 0x0D,
- 0x85,
- 0xC9,
- 0x85,
- 0x49,
- 0x84,
- 0xDD,
- 0x85,
- 0x01,
- 0x84,
- 0xFF,
- 0xF5,
- 0x00,
- 0x04,
- 0x00,
- 0x0E,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_rename_numeric{
- {16, 16},
- bitmap_icon_rename_numeric_data};
-
-static constexpr uint8_t bitmap_icon_transmit_data[] = {
- 0x80,
- 0x01,
- 0xC0,
- 0x03,
0xE0,
0x07,
- 0xB0,
- 0x0D,
- 0x98,
- 0x19,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0x83,
- 0xC1,
- 0x03,
- 0xC0,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
-};
-static constexpr Bitmap bitmap_icon_transmit{
- {16, 16},
- bitmap_icon_transmit_data};
-
-static constexpr uint8_t bitmap_icon_looking_data[] = {
- 0xF8,
- 0x01,
- 0xFC,
- 0x03,
- 0x0E,
- 0x07,
- 0x07,
- 0x0E,
- 0xF3,
- 0x0C,
- 0x9F,
- 0x0F,
- 0x9F,
- 0x0F,
- 0xF3,
- 0x0C,
- 0x07,
- 0x0E,
- 0x0E,
- 0x07,
+ 0x00,
+ 0x00,
0xFC,
+ 0x3F,
+ 0x00,
+ 0x00,
0x1F,
0xF8,
- 0x3D,
0x00,
- 0x7C,
- 0x00,
- 0xF8,
- 0x00,
- 0xF0,
- 0x00,
- 0x60,
-};
-static constexpr Bitmap bitmap_icon_looking{
- {16, 16},
- bitmap_icon_looking_data};
-
-static constexpr uint8_t bitmap_icon_delete_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x0C,
- 0x30,
- 0x1C,
- 0x38,
- 0x38,
- 0x1C,
- 0x70,
- 0x0E,
- 0xE0,
- 0x07,
0xC0,
0x03,
0xC0,
0x03,
0xE0,
+ 0x00,
+ 0x00,
0x07,
0x70,
+ 0x00,
+ 0x00,
0x0E,
0x38,
+ 0x00,
+ 0x00,
0x1C,
- 0x1C,
- 0x38,
+ 0x18,
+ 0x00,
+ 0x00,
+ 0x18,
0x0C,
+ 0x00,
+ 0x00,
0x30,
+ 0x0C,
0x00,
0x00,
+ 0x30,
+ 0x06,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_delete{
- {16, 16},
- bitmap_icon_delete_data};
-
-static constexpr uint8_t bitmap_icon_adsb_data[] = {
+ 0x60,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x03,
0x80,
0x01,
0xC0,
0x03,
- 0xC0,
- 0x03,
- 0xC0,
- 0x03,
+ 0x80,
+ 0x01,
0xC0,
0x03,
0xE0,
0x07,
- 0xF8,
- 0x1F,
- 0xFE,
- 0x7F,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xC0,
- 0x03,
- 0xC0,
- 0x03,
0xC0,
0x03,
0xE0,
0x07,
- 0xF0,
- 0x0F,
- 0xF8,
- 0x1F,
-};
-static constexpr Bitmap bitmap_icon_adsb{
- {16, 16},
- bitmap_icon_adsb_data};
-
-static constexpr uint8_t bitmap_record_data[] = {
0xC0,
- 0x07,
- 0xF0,
- 0x1F,
- 0xF8,
- 0x3F,
- 0xFC,
- 0x7F,
- 0xFC,
- 0x7F,
- 0x66,
- 0xCC,
- 0x56,
- 0xF7,
- 0x66,
- 0xF6,
- 0x56,
- 0xF7,
- 0x56,
- 0xCC,
- 0xFC,
- 0x7F,
- 0xFC,
- 0x7F,
- 0xF8,
- 0x3F,
- 0xF0,
- 0x1F,
+ 0x03,
+ 0x80,
+ 0x01,
0xC0,
- 0x07,
+ 0x03,
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x06,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_record{
- {16, 16},
- bitmap_record_data};
-
-static constexpr uint8_t bitmap_icon_options_touch_data[] = {
- 0xC7,
- 0xF1,
- 0x97,
- 0xF4,
- 0x27,
- 0xF2,
- 0x8F,
- 0xF8,
- 0x5F,
- 0xFD,
- 0x47,
- 0xFD,
- 0x53,
- 0xC1,
- 0x4B,
- 0x9F,
- 0x43,
- 0xB5,
- 0x6F,
- 0xA0,
- 0x2F,
- 0xA0,
- 0x20,
- 0x20,
0x60,
- 0x20,
- 0x40,
- 0x10,
- 0xC0,
- 0x1F,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x0C,
+ 0x00,
+ 0x00,
+ 0x30,
+ 0x0C,
+ 0x00,
+ 0x00,
+ 0x30,
+ 0x18,
0x00,
0x00,
-};
-static constexpr Bitmap bitmap_icon_options_touch{
- {16, 16},
- bitmap_icon_options_touch_data};
-
-static constexpr uint8_t bitmap_icon_peripherals_data[] = {
- 0x54,
- 0x01,
- 0x54,
- 0x01,
- 0xFF,
- 0x07,
- 0x7C,
- 0x01,
- 0xBF,
- 0x07,
- 0xDC,
0x18,
- 0x6F,
- 0x10,
- 0x2C,
- 0x21,
- 0xAF,
- 0x20,
- 0x34,
- 0x20,
- 0x54,
- 0x10,
- 0xC0,
0x38,
0x00,
- 0x77,
0x00,
+ 0x1C,
+ 0x70,
+ 0x00,
+ 0x00,
+ 0x0E,
0xE0,
0x00,
- 0xC0,
0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_peripherals{
- {16, 16},
- bitmap_icon_peripherals_data};
-
-static constexpr uint8_t bitmap_icon_capture_data[] = {
- 0xE0,
0x07,
- 0xF8,
- 0x1F,
- 0xFC,
- 0x3F,
- 0xFE,
- 0x7F,
- 0xFE,
- 0x7F,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFE,
- 0x7F,
- 0xFE,
- 0x7F,
- 0xFC,
- 0x3F,
- 0xF8,
- 0x1F,
- 0xE0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_capture{
- {16, 16},
- bitmap_icon_capture_data};
-
-static constexpr uint8_t bitmap_icon_nrf_data[] = {
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0x00,
- 0x01,
- 0xF8,
- 0x3F,
- 0xFC,
- 0x7F,
- 0xFC,
- 0x7F,
- 0xDC,
- 0x7F,
- 0x8C,
- 0x6B,
- 0xDC,
- 0x7F,
- 0xFC,
- 0x7F,
- 0xFC,
- 0x7F,
- 0xF8,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_nrf{
- {16, 16},
- bitmap_icon_nrf_data};
-
-static constexpr uint8_t bitmap_sig_noise_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x20,
- 0x00,
- 0x00,
- 0x00,
- 0x30,
- 0x80,
- 0x00,
- 0x00,
- 0x30,
- 0x80,
- 0x01,
- 0x40,
- 0x30,
0xC0,
0x03,
0xC0,
- 0x30,
- 0xC0,
0x03,
- 0xC0,
- 0x39,
- 0xC0,
- 0x72,
- 0xC0,
- 0x7B,
- 0x60,
- 0x76,
- 0x60,
- 0x6E,
- 0x60,
- 0x1E,
- 0x60,
- 0xCE,
- 0x6C,
- 0x0C,
- 0x66,
- 0xC4,
- 0x6E,
- 0x0C,
- 0x66,
- 0xC0,
- 0x3E,
- 0x00,
- 0x2C,
- 0xC0,
- 0x3B,
- 0x00,
- 0x3C,
- 0xC0,
- 0x39,
- 0x00,
- 0x3C,
- 0xC0,
- 0x18,
- 0x00,
- 0x18,
- 0x40,
- 0x10,
- 0x00,
- 0x18,
- 0x40,
- 0x10,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
0x00,
+ 0x1F,
+ 0xF8,
0x00,
0x00,
+ 0xFC,
+ 0x3F,
0x00,
0x00,
+ 0xE0,
+ 0x07,
0x00,
};
-static constexpr Bitmap bitmap_sig_noise{
+static constexpr Bitmap bitmap_target_verify{
{32, 32},
- bitmap_sig_noise_data};
+ bitmap_target_verify_data};
-static constexpr uint8_t bitmap_icon_ais_data[] = {
- 0x00,
- 0x01,
- 0x80,
- 0x01,
- 0xC0,
- 0x01,
- 0xC0,
- 0x0D,
- 0xE0,
- 0x3D,
- 0xF0,
- 0x3D,
- 0xF8,
- 0x7D,
- 0xFC,
- 0x7D,
- 0xFC,
- 0x7D,
- 0xFE,
- 0x7D,
- 0xFF,
- 0x7D,
+static constexpr uint8_t bitmap_temperature_data[] = {
0x00,
0x00,
- 0xF8,
- 0x7F,
- 0xF8,
- 0x3F,
- 0xF0,
- 0x0F,
+ 0x20,
+ 0x00,
+ 0x70,
+ 0x3E,
+ 0x88,
+ 0x00,
+ 0x88,
+ 0x00,
+ 0x88,
+ 0x3E,
+ 0x88,
+ 0x00,
+ 0x88,
+ 0x00,
+ 0x88,
+ 0x3E,
+ 0x88,
+ 0x00,
+ 0x04,
+ 0x01,
+ 0x74,
+ 0x01,
+ 0x04,
+ 0x01,
+ 0x88,
+ 0x00,
+ 0x70,
+ 0x00,
0x00,
0x00,
};
-static constexpr Bitmap bitmap_icon_ais{
+static constexpr Bitmap bitmap_temperature{
{16, 16},
- bitmap_icon_ais_data};
+ bitmap_temperature_data};
static constexpr uint8_t bitmap_titlebar_image_data[] = {
0x00,
@@ -4955,1076 +6405,6 @@ static constexpr Bitmap bitmap_titlebar_image{
{80, 16},
bitmap_titlebar_image_data};
-static constexpr uint8_t bitmap_icon_btle_data[] = {
- 0xE0,
- 0x03,
- 0x30,
- 0x07,
- 0x38,
- 0x0E,
- 0x3C,
- 0x1C,
- 0x24,
- 0x19,
- 0x0C,
- 0x13,
- 0x1C,
- 0x19,
- 0x3C,
- 0x1C,
- 0x3C,
- 0x1C,
- 0x1C,
- 0x19,
- 0x0C,
- 0x13,
- 0x24,
- 0x19,
- 0x3C,
- 0x1C,
- 0x38,
- 0x0E,
- 0x30,
- 0x07,
- 0xE0,
- 0x03,
-};
-static constexpr Bitmap bitmap_icon_btle{
- {16, 16},
- bitmap_icon_btle_data};
-
-static constexpr uint8_t bitmap_arrow_left_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x20,
- 0x00,
- 0x30,
- 0x00,
- 0x38,
- 0x00,
- 0xFC,
- 0x7F,
- 0xFE,
- 0x7F,
- 0xFC,
- 0x7F,
- 0x38,
- 0x00,
- 0x30,
- 0x00,
- 0x20,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_arrow_left{
- {16, 16},
- bitmap_arrow_left_data};
-
-static constexpr uint8_t bitmap_sd_card_error_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xC0,
- 0x1F,
- 0xE0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xF8,
- 0x1F,
- 0xC8,
- 0x13,
- 0x98,
- 0x19,
- 0x38,
- 0x1C,
- 0x78,
- 0x1E,
- 0x38,
- 0x1C,
- 0x98,
- 0x19,
- 0xC8,
- 0x13,
- 0xF8,
- 0x1F,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_sd_card_error{
- {16, 16},
- bitmap_sd_card_error_data};
-
-static constexpr uint8_t bitmap_sig_saw_up_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x06,
- 0x00,
- 0x01,
- 0x70,
- 0x06,
- 0x80,
- 0x01,
- 0x78,
- 0x06,
- 0xC0,
- 0x01,
- 0x7C,
- 0x06,
- 0xE0,
- 0x01,
- 0x6E,
- 0x06,
- 0xF0,
- 0x01,
- 0x67,
- 0x06,
- 0xB8,
- 0x81,
- 0x63,
- 0x06,
- 0x9C,
- 0xC1,
- 0x61,
- 0x06,
- 0x8E,
- 0xE1,
- 0x60,
- 0x06,
- 0x87,
- 0x71,
- 0x60,
- 0x86,
- 0x83,
- 0x39,
- 0x60,
- 0xC6,
- 0x81,
- 0x1D,
- 0x60,
- 0xE6,
- 0x80,
- 0x0F,
- 0x60,
- 0x76,
- 0x80,
- 0x07,
- 0x60,
- 0x3E,
- 0x80,
- 0x03,
- 0x60,
- 0x1E,
- 0x80,
- 0x01,
- 0x60,
- 0x0E,
- 0x80,
- 0x00,
- 0x60,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_sig_saw_up{
- {32, 32},
- bitmap_sig_saw_up_data};
-
-static constexpr uint8_t bitmap_icon_brightness_data[] = {
- 0x00,
- 0x00,
- 0x80,
- 0x01,
- 0x84,
- 0x21,
- 0x08,
- 0x10,
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xF0,
- 0x0F,
- 0xF6,
- 0x6F,
- 0xF6,
- 0x6F,
- 0xF0,
- 0x0F,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0x08,
- 0x10,
- 0x84,
- 0x21,
- 0x80,
- 0x01,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_brightness{
- {16, 16},
- bitmap_icon_brightness_data};
-
-static constexpr uint8_t bitmap_icon_microphone_data[] = {
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xE0,
- 0x07,
- 0xE0,
- 0x07,
- 0xE8,
- 0x17,
- 0xE8,
- 0x17,
- 0xE8,
- 0x17,
- 0xE8,
- 0x17,
- 0xE8,
- 0x17,
- 0xC8,
- 0x13,
- 0x18,
- 0x18,
- 0xF0,
- 0x0F,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
- 0x80,
- 0x01,
- 0xE0,
- 0x07,
-};
-static constexpr Bitmap bitmap_icon_microphone{
- {16, 16},
- bitmap_icon_microphone_data};
-
-static constexpr uint8_t bitmap_bulb_on_data[] = {
- 0x04,
- 0x3C,
- 0x20,
- 0x08,
- 0xFF,
- 0x10,
- 0x90,
- 0xFF,
- 0x09,
- 0xC0,
- 0xFF,
- 0x03,
- 0xE0,
- 0xFF,
- 0x07,
- 0xE0,
- 0xFF,
- 0x07,
- 0xF0,
- 0xE7,
- 0x0F,
- 0xF0,
- 0xBD,
- 0x0F,
- 0xF7,
- 0xBD,
- 0xEF,
- 0xF0,
- 0xDB,
- 0x0F,
- 0xF0,
- 0xDB,
- 0x0F,
- 0xE0,
- 0xDB,
- 0x07,
- 0xE0,
- 0xCB,
- 0x07,
- 0xC0,
- 0xD3,
- 0x03,
- 0x90,
- 0xCB,
- 0x09,
- 0x08,
- 0xFD,
- 0x10,
- 0x04,
- 0xE3,
- 0x20,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0x42,
- 0x00,
- 0x00,
- 0x3C,
- 0x00,
-};
-static constexpr Bitmap bitmap_bulb_on{
- {24, 24},
- bitmap_bulb_on_data};
-
-static constexpr uint8_t bitmap_sd_card_unknown_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xC0,
- 0x1F,
- 0xE0,
- 0x1F,
- 0xF0,
- 0x1F,
- 0xF8,
- 0x1F,
- 0x38,
- 0x1C,
- 0x98,
- 0x19,
- 0xF8,
- 0x1C,
- 0x78,
- 0x1E,
- 0x78,
- 0x1E,
- 0xF8,
- 0x1F,
- 0x78,
- 0x1E,
- 0xF8,
- 0x1F,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_sd_card_unknown{
- {16, 16},
- bitmap_sd_card_unknown_data};
-
-static constexpr uint8_t bitmap_icon_file_text_data[] = {
- 0xFC,
- 0x03,
- 0x04,
- 0x06,
- 0x04,
- 0x0E,
- 0x04,
- 0x1E,
- 0xF4,
- 0x3E,
- 0x04,
- 0x20,
- 0xF4,
- 0x2F,
- 0x04,
- 0x20,
- 0xF4,
- 0x2F,
- 0x04,
- 0x20,
- 0xF4,
- 0x2F,
- 0x04,
- 0x20,
- 0xF4,
- 0x2F,
- 0x04,
- 0x20,
- 0x04,
- 0x20,
- 0xFC,
- 0x3F,
-};
-static constexpr Bitmap bitmap_icon_file_text{
- {16, 16},
- bitmap_icon_file_text_data};
-
-static constexpr uint8_t bitmap_icon_ert_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x0F,
- 0x80,
- 0x7F,
- 0xC0,
- 0x0F,
- 0xFC,
- 0x0F,
- 0xC2,
- 0x0F,
- 0x82,
- 0x7F,
- 0x01,
- 0x0F,
- 0x01,
- 0x00,
- 0x21,
- 0x05,
- 0x53,
- 0x09,
- 0x56,
- 0x09,
- 0x50,
- 0x05,
- 0x50,
- 0x05,
- 0x20,
- 0xAD,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_ert{
- {16, 16},
- bitmap_icon_ert_data};
-
-static constexpr uint8_t bitmap_icon_peripherals_details_data[] = {
- 0x54,
- 0x01,
- 0x54,
- 0x01,
- 0xFF,
- 0x07,
- 0xFC,
- 0x01,
- 0x3F,
- 0x00,
- 0xBC,
- 0x3F,
- 0xBF,
- 0x60,
- 0xBC,
- 0xEE,
- 0xBF,
- 0x80,
- 0x94,
- 0xBE,
- 0x94,
- 0x80,
- 0x80,
- 0xBE,
- 0x80,
- 0x80,
- 0x80,
- 0xBE,
- 0x80,
- 0x80,
- 0x80,
- 0xFF,
-};
-static constexpr Bitmap bitmap_icon_peripherals_details{
- {16, 16},
- bitmap_icon_peripherals_details_data};
-
-static constexpr uint8_t bitmap_icon_paste_data[] = {
- 0x00,
- 0x00,
- 0xE0,
- 0x00,
- 0x18,
- 0x03,
- 0xE4,
- 0x04,
- 0x04,
- 0x04,
- 0x04,
- 0x04,
- 0x84,
- 0x3F,
- 0x84,
- 0x20,
- 0x84,
- 0x2E,
- 0x84,
- 0x20,
- 0x84,
- 0x2E,
- 0x84,
- 0x20,
- 0x84,
- 0x2E,
- 0xF8,
- 0x20,
- 0x80,
- 0x3F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_paste{
- {16, 16},
- bitmap_icon_paste_data};
-
-static constexpr uint8_t bitmap_bulb_ignore_data[] = {
- 0x00,
- 0x3C,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x80,
- 0x00,
- 0x01,
- 0x40,
- 0x3C,
- 0x02,
- 0x20,
- 0x7E,
- 0x04,
- 0x20,
- 0xE7,
- 0x04,
- 0x10,
- 0xC3,
- 0x08,
- 0x10,
- 0xE3,
- 0x08,
- 0x10,
- 0x70,
- 0x08,
- 0x10,
- 0x38,
- 0x08,
- 0x10,
- 0x18,
- 0x08,
- 0x20,
- 0x18,
- 0x04,
- 0x20,
- 0x00,
- 0x04,
- 0x40,
- 0x18,
- 0x02,
- 0x80,
- 0x18,
- 0x01,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xFF,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0xC3,
- 0x00,
- 0x00,
- 0xBD,
- 0x00,
- 0x00,
- 0x42,
- 0x00,
- 0x00,
- 0x3C,
- 0x00,
-};
-static constexpr Bitmap bitmap_bulb_ignore{
- {24, 24},
- bitmap_bulb_ignore_data};
-
-static constexpr uint8_t bitmap_icon_gps_sim_data[] = {
- 0xC0,
- 0x07,
- 0xE0,
- 0x0F,
- 0x70,
- 0x1F,
- 0x78,
- 0x3E,
- 0x78,
- 0x3C,
- 0x78,
- 0x38,
- 0x78,
- 0x30,
- 0x78,
- 0x38,
- 0x78,
- 0x3C,
- 0x70,
- 0x1E,
- 0x70,
- 0x1F,
- 0xE0,
- 0x0F,
- 0xC0,
- 0x07,
- 0x80,
- 0x03,
- 0x20,
- 0x09,
- 0x50,
- 0x14,
-};
-static constexpr Bitmap bitmap_icon_gps_sim{
- {16, 16},
- bitmap_icon_gps_sim_data};
-
-static constexpr uint8_t bitmap_icon_options_datetime_data[] = {
- 0x0C,
- 0x06,
- 0xFF,
- 0x1F,
- 0x49,
- 0x12,
- 0x49,
- 0x12,
- 0xFF,
- 0x1F,
- 0x49,
- 0x00,
- 0x49,
- 0x1C,
- 0x7F,
- 0x63,
- 0x09,
- 0x49,
- 0x89,
- 0x88,
- 0xBE,
- 0xB8,
- 0x80,
- 0x80,
- 0x00,
- 0x41,
- 0x00,
- 0x63,
- 0x00,
- 0x1C,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_options_datetime{
- {16, 16},
- bitmap_icon_options_datetime_data};
-
-static constexpr uint8_t bitmap_target_verify_data[] = {
- 0x00,
- 0xE0,
- 0x07,
- 0x00,
- 0x00,
- 0xFC,
- 0x3F,
- 0x00,
- 0x00,
- 0x1F,
- 0xF8,
- 0x00,
- 0xC0,
- 0x03,
- 0xC0,
- 0x03,
- 0xE0,
- 0x00,
- 0x00,
- 0x07,
- 0x70,
- 0x00,
- 0x00,
- 0x0E,
- 0x38,
- 0x00,
- 0x00,
- 0x1C,
- 0x18,
- 0x00,
- 0x00,
- 0x18,
- 0x0C,
- 0x00,
- 0x00,
- 0x30,
- 0x0C,
- 0x00,
- 0x00,
- 0x30,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x03,
- 0x80,
- 0x01,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0xE0,
- 0x07,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
- 0xC0,
- 0x03,
- 0x80,
- 0x01,
- 0xC0,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x06,
- 0x00,
- 0x00,
- 0x60,
- 0x0C,
- 0x00,
- 0x00,
- 0x30,
- 0x0C,
- 0x00,
- 0x00,
- 0x30,
- 0x18,
- 0x00,
- 0x00,
- 0x18,
- 0x38,
- 0x00,
- 0x00,
- 0x1C,
- 0x70,
- 0x00,
- 0x00,
- 0x0E,
- 0xE0,
- 0x00,
- 0x00,
- 0x07,
- 0xC0,
- 0x03,
- 0xC0,
- 0x03,
- 0x00,
- 0x1F,
- 0xF8,
- 0x00,
- 0x00,
- 0xFC,
- 0x3F,
- 0x00,
- 0x00,
- 0xE0,
- 0x07,
- 0x00,
-};
-static constexpr Bitmap bitmap_target_verify{
- {32, 32},
- bitmap_target_verify_data};
-
-static constexpr uint8_t bitmap_icon_new_category_data[] = {
- 0x00,
- 0x18,
- 0x3E,
- 0x18,
- 0x41,
- 0x7E,
- 0xC1,
- 0x7E,
- 0xFF,
- 0x18,
- 0xFF,
- 0xDB,
- 0xFF,
- 0xC3,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xAF,
- 0xEA,
- 0x57,
- 0xF5,
- 0xEF,
- 0xEF,
- 0xF7,
- 0xF7,
- 0xEE,
- 0x6F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_new_category{
- {16, 16},
- bitmap_icon_new_category_data};
-
-static constexpr uint8_t bitmap_target_data[] = {
- 0x80,
- 0x00,
- 0x80,
- 0x00,
- 0xE0,
- 0x03,
- 0x90,
- 0x04,
- 0x88,
- 0x08,
- 0x04,
- 0x10,
- 0x04,
- 0x10,
- 0x1F,
- 0x7C,
- 0x04,
- 0x10,
- 0x04,
- 0x10,
- 0x88,
- 0x08,
- 0x90,
- 0x04,
- 0xE0,
- 0x03,
- 0x80,
- 0x00,
- 0x80,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_target{
- {16, 16},
- bitmap_target_data};
-
-static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
- 0x40,
- 0x00,
- 0x60,
- 0x44,
- 0x70,
- 0x6C,
- 0x7C,
- 0x38,
- 0x7C,
- 0x10,
- 0x7C,
- 0x38,
- 0x70,
- 0x6C,
- 0x60,
- 0x44,
- 0x40,
- 0x00,
- 0x00,
- 0x44,
- 0x30,
- 0x6C,
- 0x48,
- 0x38,
- 0x84,
- 0x10,
- 0x84,
- 0x38,
- 0x86,
- 0x6D,
- 0x86,
- 0x45,
-};
-static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
- {16, 16},
- bitmap_icon_speaker_and_headphones_mute_data};
-
-static constexpr uint8_t bitmap_icon_new_dir_data[] = {
- 0x00,
- 0x00,
- 0x1E,
- 0x00,
- 0x21,
- 0x00,
- 0xE1,
- 0x7F,
- 0x01,
- 0xC0,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0xF1,
- 0x8F,
- 0xF1,
- 0x8F,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0x81,
- 0x03,
- 0xC0,
- 0xFE,
- 0x7F,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_new_dir{
- {16, 16},
- bitmap_icon_new_dir_data};
-
-static constexpr uint8_t bitmap_icon_protoview_data[] = {
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xF8,
- 0x87,
- 0x08,
- 0x84,
- 0x0F,
- 0xFC,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xF3,
- 0xE0,
- 0x92,
- 0xA0,
- 0x9E,
- 0xBF,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0xFC,
- 0xF3,
- 0x04,
- 0x12,
- 0x07,
- 0x1E,
-};
-static constexpr Bitmap bitmap_icon_protoview{
- {16, 16},
- bitmap_icon_protoview_data};
-
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
diff --git a/firmware/application/bitmaps/bmp_modal_warning.hpp b/firmware/application/bitmaps/bmp_modal_warning.hpp
deleted file mode 100644
index 4af35e884..000000000
--- a/firmware/application/bitmaps/bmp_modal_warning.hpp
+++ /dev/null
@@ -1,72 +0,0 @@
-unsigned char modal_warning_bmp[] = {
- 0x42, 0x4d, 0xfa, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x00,
- 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0xa8, 0x02,
- 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x07, 0x00,
- 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0x00, 0x00, 0x5a,
- 0xff, 0x00, 0x78, 0x78, 0x78, 0x00, 0x00, 0x7c, 0xff, 0x00, 0x00, 0xa9,
- 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x02, 0x03, 0x24, 0x66, 0x02, 0x30,
- 0x00, 0x00, 0x02, 0x36, 0x24, 0x66, 0x02, 0x63, 0x00, 0x00, 0x02, 0x66,
- 0x02, 0x63, 0x20, 0x00, 0x02, 0x36, 0x02, 0x66, 0x00, 0x00, 0x02, 0x36,
- 0x02, 0x60, 0x20, 0x00, 0x02, 0x06, 0x02, 0x63, 0x00, 0x00, 0x02, 0x06,
- 0x02, 0x60, 0x0c, 0x00, 0x00, 0x08, 0x03, 0x66, 0x66, 0x30, 0x0c, 0x00,
- 0x02, 0x06, 0x02, 0x60, 0x00, 0x00, 0x02, 0x03, 0x02, 0x66, 0x0c, 0x00,
- 0x00, 0x08, 0x06, 0x11, 0x11, 0x60, 0x0c, 0x00, 0x02, 0x66, 0x02, 0x30,
- 0x00, 0x00, 0x02, 0x00, 0x02, 0x66, 0x0c, 0x00, 0x00, 0x08, 0x06, 0x21,
- 0x21, 0x60, 0x0c, 0x00, 0x02, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06,
- 0x00, 0x36, 0x60, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x42, 0x42, 0x60,
- 0x0a, 0x00, 0x00, 0x06, 0x06, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
- 0x00, 0x06, 0x60, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x54, 0x45, 0x60,
- 0x0a, 0x00, 0x00, 0x06, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
- 0x00, 0x03, 0x66, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x03, 0x66, 0x66, 0x30,
- 0x0a, 0x00, 0x00, 0x06, 0x66, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
- 0x00, 0x00, 0x66, 0x00, 0x1c, 0x00, 0x00, 0x06, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x36, 0x60, 0x18, 0x00, 0x00, 0x08,
- 0x06, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x06, 0x60,
- 0x0a, 0x00, 0x02, 0x36, 0x02, 0x63, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x66, 0x08, 0x00,
- 0x00, 0x08, 0x03, 0x61, 0x16, 0x30, 0x08, 0x00, 0x00, 0x08, 0x66, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x66, 0x08, 0x00, 0x00, 0x08,
- 0x06, 0x11, 0x11, 0x60, 0x08, 0x00, 0x02, 0x66, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x02, 0x36, 0x02, 0x60, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
- 0x11, 0x60, 0x06, 0x00, 0x02, 0x06, 0x02, 0x63, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x02, 0x06, 0x02, 0x60, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
- 0x11, 0x60, 0x06, 0x00, 0x02, 0x06, 0x02, 0x60, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x02, 0x03, 0x02, 0x66, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
- 0x11, 0x60, 0x06, 0x00, 0x02, 0x66, 0x02, 0x30, 0x06, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x02, 0x66, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11, 0x11, 0x60,
- 0x06, 0x00, 0x02, 0x66, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x18,
- 0x36, 0x60, 0x00, 0x00, 0x06, 0x11, 0x11, 0x60, 0x00, 0x00, 0x06, 0x63,
- 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x18, 0x06, 0x60, 0x00, 0x00,
- 0x06, 0x21, 0x21, 0x60, 0x00, 0x00, 0x06, 0x60, 0x08, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x18, 0x03, 0x66, 0x00, 0x00, 0x06, 0x12, 0x12, 0x60,
- 0x00, 0x00, 0x66, 0x30, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14,
- 0x66, 0x00, 0x00, 0x06, 0x21, 0x21, 0x60, 0x00, 0x00, 0x66, 0x0a, 0x00,
- 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14, 0x36, 0x60, 0x00, 0x06, 0x22, 0x22,
- 0x60, 0x00, 0x06, 0x63, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14,
- 0x06, 0x60, 0x00, 0x06, 0x24, 0x24, 0x60, 0x00, 0x06, 0x60, 0x0a, 0x00,
- 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14, 0x03, 0x66, 0x00, 0x06, 0x42, 0x42,
- 0x60, 0x00, 0x66, 0x30, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x10,
- 0x66, 0x00, 0x06, 0x54, 0x45, 0x60, 0x00, 0x66, 0x0c, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x10, 0x36, 0x60, 0x03, 0x66, 0x66, 0x30, 0x06, 0x63,
- 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x06, 0x02, 0x60, 0x08, 0x00,
- 0x02, 0x06, 0x02, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x03,
- 0x02, 0x66, 0x08, 0x00, 0x02, 0x66, 0x02, 0x30, 0x0c, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x02, 0x66, 0x08, 0x00, 0x02, 0x66, 0x0e, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x0c, 0x36, 0x60, 0x00, 0x00, 0x06, 0x63, 0x0e, 0x00,
- 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x06, 0x60, 0x00, 0x00, 0x06, 0x60,
- 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x03, 0x66, 0x00, 0x00,
- 0x66, 0x30, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x66, 0x00,
- 0x00, 0x66, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x36, 0x60,
- 0x06, 0x63, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x06, 0x60,
- 0x06, 0x60, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x03, 0x66,
- 0x66, 0x30, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x66, 0x02, 0x66,
- 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x36, 0x02, 0x63, 0x12, 0x00,
- 0x00, 0x01};
-unsigned int modal_warning_bmp_len = 830;
diff --git a/firmware/application/bitmaps/bmp_splash.hpp b/firmware/application/bitmaps/bmp_splash.hpp
deleted file mode 100644
index 77af970c0..000000000
--- a/firmware/application/bitmaps/bmp_splash.hpp
+++ /dev/null
@@ -1,327 +0,0 @@
-// converted by bmp_to_hex_cpp_arr.py at the firmware/tools dir
-// fake transparent px should be the exact color rgb dec(41,24,22), then use true for the last arg when drawing bmp with the draw func
-
-const unsigned char splash_bmp[] = {
- 0x42, 0x4d, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x00,
- 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xe6, 0x00, 0x00, 0x00, 0x32, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x9c, 0x0e,
- 0x00, 0x00, 0x12, 0x0b, 0x00, 0x00, 0x12, 0x0b, 0x00, 0x00, 0x0f, 0x00,
- 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x19, 0xf6, 0x00, 0x00, 0x14,
- 0xc9, 0x00, 0x00, 0x0c, 0x78, 0x00, 0x16, 0x18, 0x29, 0x00, 0xfd, 0xfd,
- 0xfd, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0xe7, 0xe7, 0xe7, 0x00, 0xda, 0xda,
- 0xda, 0x00, 0xcd, 0xcd, 0xcd, 0x00, 0xc7, 0xc7, 0xc7, 0x00, 0xba, 0xba,
- 0xba, 0x00, 0xad, 0xad, 0xad, 0x00, 0x96, 0x96, 0x96, 0x00, 0x5c, 0x5c,
- 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
- 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0x00, 0x08,
- 0x33, 0x44, 0x44, 0x47, 0x0a, 0x33, 0x00, 0x06, 0xd5, 0x44, 0x7d, 0x00,
- 0x0a, 0x33, 0x00, 0x0a, 0x44, 0x44, 0x48, 0x33, 0x3a, 0x00, 0x06, 0x44,
- 0x02, 0xd3, 0x12, 0x33, 0x00, 0x08, 0xa4, 0x44, 0x45, 0x9d, 0x08, 0x33,
- 0x00, 0x08, 0xc4, 0x44, 0x45, 0xc3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x44,
- 0x44, 0x6d, 0x16, 0x33, 0x00, 0x08, 0x34, 0x44, 0x44, 0x6d, 0x08, 0x33,
- 0x02, 0xd5, 0x1a, 0x44, 0x00, 0x0c, 0x45, 0xc3, 0x33, 0x44, 0x44, 0x47,
- 0x0a, 0x33, 0x00, 0x06, 0xd5, 0x44, 0x7d, 0x00, 0x0a, 0x33, 0x00, 0x08,
- 0x44, 0x44, 0x48, 0x33, 0x00, 0x00, 0x00, 0x0a, 0x33, 0x49, 0x99, 0x95,
- 0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a, 0xd4, 0x46, 0x89, 0x98, 0xb3, 0x00,
- 0x08, 0x33, 0x00, 0x12, 0x49, 0x99, 0x94, 0x33, 0x3d, 0x79, 0x99, 0x99,
- 0x73, 0x00, 0x10, 0x33, 0x00, 0x0a, 0x3d, 0x47, 0x99, 0x95, 0xa3, 0x00,
- 0x08, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
- 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c,
- 0x06, 0x33, 0x00, 0x06, 0xb4, 0x56, 0x89, 0x00, 0x18, 0x99, 0x00, 0x0e,
- 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a,
- 0xd4, 0x46, 0x89, 0x98, 0xb3, 0x00, 0x08, 0x33, 0x00, 0x08, 0x49, 0x99,
- 0x94, 0x33, 0x00, 0x00, 0x00, 0x0a, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00,
- 0x06, 0x33, 0x02, 0x55, 0x06, 0x99, 0x02, 0x9b, 0x08, 0x33, 0x00, 0x12,
- 0x49, 0x99, 0x94, 0x33, 0x33, 0xb9, 0x99, 0x99, 0x8d, 0x00, 0x10, 0x33,
- 0x00, 0x0a, 0x37, 0x69, 0x99, 0x74, 0xd3, 0x00, 0x08, 0x33, 0x00, 0x08,
- 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c,
- 0x16, 0x33, 0x00, 0x10, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x33, 0x3a, 0x47,
- 0x1c, 0x99, 0x00, 0x0e, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00,
- 0x06, 0x33, 0x02, 0x55, 0x06, 0x99, 0x02, 0x9b, 0x08, 0x33, 0x00, 0x08,
- 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95,
- 0xd3, 0x33, 0x33, 0x3a, 0x48, 0x00, 0x06, 0x99, 0x02, 0x98, 0x02, 0xd3,
- 0x06, 0x33, 0x00, 0x12, 0x49, 0x99, 0x94, 0x33, 0x33, 0xd7, 0x99, 0x99,
- 0x98, 0x00, 0x10, 0x33, 0x00, 0x08, 0xd4, 0x89, 0x99, 0x5a, 0x0a, 0x33,
- 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99,
- 0x99, 0x7c, 0x16, 0x33, 0x00, 0x10, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x33,
- 0xb4, 0x79, 0x1c, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95,
- 0xd3, 0x33, 0x33, 0x3a, 0x48, 0x00, 0x06, 0x99, 0x02, 0x98, 0x02, 0xd3,
- 0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x12,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x33, 0xd4, 0x79, 0x00, 0x08, 0x99,
- 0x02, 0x73, 0x06, 0x33, 0x00, 0x0c, 0x49, 0x99, 0x94, 0x33, 0x33, 0x3b,
- 0x06, 0x99, 0x02, 0xc3, 0x0e, 0x33, 0x00, 0x08, 0x56, 0x99, 0x97, 0x4d,
- 0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
- 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
- 0x33, 0x3d, 0x47, 0x00, 0x1e, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49,
- 0x99, 0x95, 0xd3, 0x33, 0x33, 0xd4, 0x79, 0x00, 0x08, 0x99, 0x02, 0x73,
- 0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x10,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x33, 0xa5, 0x0a, 0x99, 0x02, 0x8d,
- 0x06, 0x33, 0x00, 0x14, 0x49, 0x99, 0x94, 0x33, 0x33, 0x3d, 0x79, 0x99,
- 0x99, 0x73, 0x0c, 0x33, 0x00, 0x0a, 0x3c, 0x48, 0x99, 0x95, 0xa3, 0x00,
- 0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
- 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
- 0x33, 0x37, 0x69, 0x00, 0x1e, 0x99, 0x00, 0x14, 0x97, 0xa3, 0x33, 0x49,
- 0x99, 0x95, 0xd3, 0x33, 0x33, 0xa5, 0x0a, 0x99, 0x02, 0x8d, 0x06, 0x33,
- 0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x1c, 0x33, 0x49,
- 0x99, 0x95, 0xd3, 0x33, 0x3d, 0x47, 0x99, 0x97, 0x89, 0x99, 0x99, 0x97,
- 0x06, 0x33, 0x00, 0x06, 0x49, 0x99, 0x94, 0x00, 0x06, 0x33, 0x00, 0x08,
- 0xb9, 0x99, 0x99, 0x9d, 0x0c, 0x33, 0x00, 0x0a, 0x34, 0x69, 0x99, 0x74,
- 0xd3, 0x00, 0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x98, 0xa3, 0x10, 0x33,
- 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99,
- 0x99, 0x7c, 0x33, 0x34, 0x89, 0x99, 0x64, 0x00, 0x1c, 0x44, 0x00, 0x1e,
- 0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3d, 0x47, 0x99, 0x97, 0x89,
- 0x99, 0x99, 0x97, 0x00, 0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33,
- 0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3c, 0x59,
- 0x99, 0x95, 0xbc, 0x99, 0x99, 0x98, 0xd3, 0x33, 0x33, 0x49, 0x99, 0x94,
- 0x06, 0x33, 0x02, 0xd7, 0x06, 0x99, 0x02, 0x74, 0x0c, 0x44, 0x00, 0x06,
- 0x99, 0x99, 0x4b, 0x00, 0x0c, 0x33, 0x00, 0x08, 0xb7, 0x99, 0x99, 0xa3,
- 0x10, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x12,
- 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd5, 0x99, 0x99, 0x6d, 0x00, 0x20, 0x33,
- 0x00, 0x28, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3c, 0x59, 0x99, 0x95, 0xbc,
- 0x99, 0x99, 0x98, 0xd3, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00,
- 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x34, 0x79, 0x99, 0x84,
- 0xdd, 0x89, 0x99, 0x99, 0x83, 0x33, 0x33, 0x49, 0x99, 0x94, 0x06, 0x33,
- 0x02, 0x3b, 0x16, 0x99, 0x02, 0x97, 0x02, 0x43, 0x0a, 0x33, 0x00, 0x0a,
- 0x3d, 0x47, 0x99, 0x99, 0x9d, 0x00, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99,
- 0x99, 0x9d, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6,
- 0x99, 0x99, 0x6d, 0x00, 0x20, 0x33, 0x00, 0x28, 0x49, 0x99, 0x95, 0xd3,
- 0x33, 0x34, 0x79, 0x99, 0x84, 0xdd, 0x89, 0x99, 0x99, 0x83, 0x33, 0x33,
- 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95,
- 0xd3, 0x33, 0xc4, 0x99, 0x99, 0x67, 0x33, 0x79, 0x99, 0x99, 0x8d, 0x33,
- 0x33, 0x49, 0x99, 0x94, 0x06, 0x33, 0x02, 0x3d, 0x02, 0x79, 0x14, 0x99,
- 0x02, 0x94, 0x02, 0xc3, 0x0a, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x99,
- 0x98, 0x00, 0x10, 0x33, 0x02, 0x34, 0x06, 0x99, 0x02, 0x74, 0x16, 0x44,
- 0x00, 0x10, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x9d, 0x20, 0x33,
- 0x00, 0x28, 0x49, 0x99, 0x95, 0xd3, 0x33, 0xc4, 0x99, 0x99, 0x67, 0x33,
- 0x79, 0x99, 0x99, 0x8d, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00,
- 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x57, 0x99, 0x99, 0x4d,
- 0x33, 0xd8, 0x99, 0x99, 0x98, 0x33, 0x33, 0x49, 0x99, 0x94, 0x08, 0x33,
- 0x02, 0xc9, 0x14, 0x99, 0x02, 0x74, 0x0c, 0x33, 0x02, 0x84, 0x02, 0x89,
- 0x06, 0x99, 0x02, 0xa3, 0x0e, 0x33, 0x02, 0x34, 0x22, 0x99, 0x00, 0x06,
- 0x7c, 0x33, 0xd6, 0x00, 0x06, 0x99, 0x02, 0x64, 0x18, 0x44, 0x00, 0x2e,
- 0x45, 0xc3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x57, 0x99, 0x99, 0x4d,
- 0x33, 0xd8, 0x99, 0x99, 0x98, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x3d, 0x48, 0x99,
- 0x97, 0x43, 0x33, 0x37, 0x99, 0x99, 0x97, 0xd3, 0x33, 0x49, 0x99, 0x94,
- 0x08, 0x33, 0x02, 0x37, 0x14, 0x99, 0x02, 0x4c, 0x0a, 0x33, 0x02, 0x3b,
- 0x02, 0x48, 0x08, 0x99, 0x02, 0x9b, 0x0e, 0x33, 0x02, 0x34, 0x22, 0x99,
- 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x2e, 0x97, 0xa3,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0x3d, 0x48, 0x99, 0x97, 0x43, 0x33, 0x37,
- 0x99, 0x99, 0x97, 0xd3, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x35, 0x69, 0x99, 0x95, 0xc3,
- 0x33, 0x3c, 0x89, 0x99, 0x99, 0xb3, 0x33, 0x49, 0x99, 0x94, 0x08, 0x33,
- 0x02, 0x3c, 0x12, 0x99, 0x02, 0x97, 0x02, 0x43, 0x0a, 0x33, 0x02, 0xd4,
- 0x02, 0x79, 0x0a, 0x99, 0x02, 0xd3, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
- 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x2e, 0x97, 0xa3,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0x35, 0x69, 0x99, 0x95, 0xc3, 0x33, 0x3c,
- 0x89, 0x99, 0x99, 0xb3, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0xd3, 0xd4, 0x89, 0x99, 0x74, 0x00,
- 0x06, 0x33, 0x00, 0x10, 0x79, 0x99, 0x99, 0x7d, 0x33, 0x49, 0x99, 0x94,
- 0x0a, 0x33, 0x00, 0x16, 0x79, 0x99, 0x99, 0x64, 0x44, 0x44, 0x89, 0x99,
- 0x99, 0x94, 0xc3, 0x00, 0x08, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x98,
- 0x59, 0x00, 0x06, 0x99, 0x02, 0x8d, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
- 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x16, 0x97, 0xa3,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0xd4, 0x89, 0x99, 0x74, 0x00, 0x06, 0x33,
- 0x00, 0x12, 0x79, 0x99, 0x99, 0x7d, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x76, 0x99, 0x99,
- 0x6a, 0x00, 0x06, 0x33, 0x00, 0x10, 0xa9, 0x99, 0x99, 0x9b, 0x33, 0x49,
- 0x99, 0x94, 0x0a, 0x33, 0x00, 0x14, 0xc9, 0x99, 0x99, 0x9c, 0x33, 0x33,
- 0x3b, 0x99, 0x99, 0x74, 0x0a, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x95,
- 0x7d, 0x00, 0x06, 0x99, 0x02, 0x9a, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
- 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x16, 0x97, 0xa3,
- 0x33, 0x49, 0x99, 0x95, 0xd3, 0x76, 0x99, 0x99, 0x6a, 0x00, 0x06, 0x33,
- 0x00, 0x12, 0xa9, 0x99, 0x99, 0x9b, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0x3d, 0x48, 0x99, 0x98,
- 0x4d, 0x00, 0x06, 0x33, 0x00, 0x10, 0xd7, 0x99, 0x99, 0x97, 0xd3, 0x49,
- 0x99, 0x94, 0x0a, 0x33, 0x00, 0x14, 0x37, 0x99, 0x99, 0x97, 0x33, 0x33,
- 0xc4, 0x89, 0x99, 0x4c, 0x0a, 0x33, 0x00, 0x0c, 0x84, 0x89, 0x99, 0x64,
- 0x33, 0xd9, 0x06, 0x99, 0x02, 0xa3, 0x0a, 0x33, 0x00, 0x08, 0x34, 0x99,
- 0x99, 0x64, 0x16, 0x44, 0x06, 0x99, 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00,
- 0x20, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0x3d, 0x48,
- 0x99, 0x98, 0x4d, 0x00, 0x06, 0x33, 0x00, 0x12, 0xd7, 0x99, 0x99, 0x97,
- 0xd3, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49,
- 0x99, 0x96, 0x3b, 0x69, 0x99, 0x96, 0x53, 0x00, 0x06, 0x33, 0x02, 0x38,
- 0x06, 0x99, 0x00, 0x08, 0xc3, 0x49, 0x99, 0x94, 0x0a, 0x33, 0x02, 0x3c,
- 0x06, 0x99, 0x00, 0x0c, 0xb3, 0x33, 0x46, 0x99, 0x96, 0x43, 0x08, 0x33,
- 0x00, 0x0e, 0x3b, 0x48, 0x99, 0x97, 0x4d, 0x33, 0x3d, 0x00, 0x06, 0x99,
- 0x02, 0x9c, 0x0a, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33,
- 0x00, 0x12, 0x39, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x54, 0x00,
- 0x1c, 0x44, 0x00, 0x14, 0xa3, 0x33, 0x49, 0x99, 0x96, 0x3b, 0x69, 0x99,
- 0x96, 0x53, 0x06, 0x33, 0x02, 0x38, 0x06, 0x99, 0x00, 0x0a, 0xc3, 0x49,
- 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x98,
- 0xd5, 0x79, 0x99, 0x94, 0xd3, 0x00, 0x06, 0x33, 0x00, 0x10, 0x3d, 0x89,
- 0x99, 0x99, 0x73, 0x49, 0x99, 0x94, 0x0c, 0x33, 0x00, 0x12, 0x79, 0x99,
- 0x99, 0x8d, 0x3a, 0x59, 0x99, 0x84, 0xd3, 0x00, 0x08, 0x33, 0x00, 0x10,
- 0xd4, 0x79, 0x99, 0x84, 0xb3, 0x33, 0x33, 0xb9, 0x06, 0x99, 0x02, 0xd3,
- 0x08, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x12,
- 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x5d, 0x00, 0x20, 0x33,
- 0x00, 0x10, 0x49, 0x99, 0x98, 0xd5, 0x79, 0x99, 0x94, 0xd3, 0x06, 0x33,
- 0x00, 0x12, 0x3d, 0x89, 0x99, 0x99, 0x73, 0x49, 0x99, 0x94, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x33, 0x49, 0x99, 0x99, 0xa6, 0x99, 0x99, 0x74,
- 0x0a, 0x33, 0x00, 0x0e, 0x79, 0x99, 0x99, 0x9c, 0x59, 0x99, 0x94, 0x00,
- 0x0c, 0x33, 0x00, 0x10, 0xc9, 0x99, 0x99, 0x9a, 0xd4, 0x79, 0x99, 0x65,
- 0x08, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x98, 0x48, 0x00, 0x06, 0x33,
- 0x02, 0x3a, 0x06, 0x99, 0x02, 0x83, 0x08, 0x33, 0x00, 0x08, 0x34, 0x99,
- 0x99, 0x6d, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6,
- 0x99, 0x99, 0x93, 0x00, 0x20, 0x33, 0x00, 0x0e, 0x49, 0x99, 0x99, 0xa6,
- 0x99, 0x99, 0x74, 0x00, 0x0a, 0x33, 0x00, 0x10, 0x79, 0x99, 0x99, 0x9c,
- 0x59, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x10, 0x33, 0x49, 0x99, 0x99,
- 0x98, 0x99, 0x99, 0x5c, 0x0a, 0x33, 0x02, 0xd8, 0x06, 0x99, 0x00, 0x06,
- 0x79, 0x99, 0x84, 0x00, 0x0c, 0x33, 0x02, 0x37, 0x06, 0x99, 0x00, 0x08,
- 0x86, 0x99, 0x98, 0x4d, 0x08, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x95,
- 0x53, 0x00, 0x08, 0x33, 0x00, 0x08, 0x89, 0x99, 0x99, 0x9a, 0x08, 0x33,
- 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x0c, 0x34, 0x99,
- 0x99, 0x7c, 0x33, 0x36, 0x06, 0x99, 0x02, 0x54, 0x18, 0x44, 0x00, 0x14,
- 0x45, 0xc3, 0x33, 0x49, 0x99, 0x99, 0x98, 0x99, 0x99, 0x5c, 0x0a, 0x33,
- 0x02, 0xd8, 0x06, 0x99, 0x00, 0x08, 0x79, 0x99, 0x84, 0x33, 0x00, 0x00,
- 0x02, 0x33, 0x02, 0x59, 0x08, 0x99, 0x02, 0x97, 0x02, 0x4d, 0x0a, 0x33,
- 0x02, 0x36, 0x0a, 0x99, 0x02, 0x75, 0x0c, 0x33, 0x02, 0x3c, 0x0a, 0x99,
- 0x02, 0x96, 0x02, 0x53, 0x08, 0x33, 0x00, 0x0a, 0x84, 0x89, 0x99, 0x64,
- 0xd3, 0x00, 0x08, 0x33, 0x02, 0xd9, 0x06, 0x99, 0x02, 0xb3, 0x06, 0x33,
- 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x0c, 0x34, 0x99,
- 0x99, 0x7c, 0x33, 0x36, 0x20, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0x59,
- 0x08, 0x99, 0x02, 0x97, 0x02, 0x4d, 0x0a, 0x33, 0x02, 0x36, 0x0a, 0x99,
- 0x02, 0x75, 0x02, 0x33, 0x00, 0x00, 0x02, 0x33, 0x02, 0xa8, 0x08, 0x99,
- 0x02, 0x95, 0x02, 0xa3, 0x0a, 0x33, 0x02, 0x3c, 0x0a, 0x99, 0x02, 0x5b,
- 0x0e, 0x33, 0x02, 0x89, 0x08, 0x99, 0x02, 0x84, 0x02, 0xd3, 0x06, 0x33,
- 0x00, 0x0a, 0x3b, 0x48, 0x99, 0x97, 0x4c, 0x00, 0x0a, 0x33, 0x02, 0x3d,
- 0x06, 0x99, 0x02, 0x9d, 0x06, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d,
- 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x3d, 0x89, 0x00,
- 0x1e, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0xa8, 0x08, 0x99, 0x02, 0x95,
- 0x02, 0xa3, 0x0a, 0x33, 0x02, 0x3c, 0x0a, 0x99, 0x02, 0x5b, 0x02, 0x33,
- 0x00, 0x00, 0x02, 0x33, 0x02, 0xd7, 0x08, 0x99, 0x02, 0x64, 0x0e, 0x33,
- 0x02, 0x79, 0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd8,
- 0x06, 0x99, 0x02, 0x98, 0x02, 0x48, 0x08, 0x33, 0x00, 0x0a, 0xd4, 0x79,
- 0x99, 0x84, 0xb3, 0x00, 0x0c, 0x33, 0x00, 0x16, 0xb9, 0x99, 0x99, 0x98,
- 0xd3, 0x33, 0x33, 0x34, 0x99, 0x99, 0x6d, 0x00, 0x16, 0x33, 0x00, 0x0c,
- 0x34, 0x99, 0x99, 0x7c, 0x33, 0x33, 0x20, 0x99, 0x00, 0x08, 0x97, 0xa3,
- 0x33, 0xd7, 0x08, 0x99, 0x02, 0x64, 0x0e, 0x33, 0x02, 0x79, 0x06, 0x99,
- 0x00, 0x06, 0x96, 0x4d, 0x33, 0x00, 0x00, 0x00, 0x02, 0x33, 0x02, 0x3c,
- 0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd9, 0x06, 0x99,
- 0x02, 0x74, 0x02, 0xc3, 0x0e, 0x33, 0x02, 0x3d, 0x06, 0x99, 0x02, 0x85,
- 0x02, 0x53, 0x06, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x99, 0x57, 0x00,
- 0x0e, 0x33, 0x02, 0x39, 0x06, 0x99, 0x00, 0x0e, 0xa3, 0x33, 0x33, 0x34,
- 0x99, 0x99, 0x6d, 0x00, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
- 0x33, 0x33, 0x3a, 0x00, 0x1e, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0x3c,
- 0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd9, 0x06, 0x99,
- 0x00, 0x06, 0x74, 0xc3, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x33, 0x33,
- 0xc8, 0x99, 0x96, 0x44, 0xd3, 0x00, 0x0e, 0x33, 0x00, 0x0a, 0x3d, 0x79,
- 0x99, 0x76, 0x4b, 0x00, 0x12, 0x33, 0x00, 0x0a, 0xc7, 0x99, 0x96, 0x45,
- 0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x96, 0x43, 0x00,
- 0x10, 0x33, 0x00, 0x14, 0x89, 0x99, 0x99, 0x9a, 0x33, 0x33, 0x34, 0x99,
- 0x99, 0x6d, 0x16, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x06, 0x33,
- 0x02, 0xa6, 0x1c, 0x99, 0x00, 0x12, 0x97, 0xa3, 0x33, 0x33, 0xc8, 0x99,
- 0x96, 0x44, 0xd3, 0x00, 0x0e, 0x33, 0x00, 0x0e, 0x3d, 0x79, 0x99, 0x76,
- 0x4b, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x33, 0x33, 0x3d, 0x84,
- 0x44, 0xc3, 0x12, 0x33, 0x00, 0x08, 0xdc, 0x44, 0x4a, 0xd3, 0x12, 0x33,
- 0x00, 0x08, 0x3d, 0xa4, 0x44, 0xd3, 0x08, 0x33, 0x02, 0xc8, 0x06, 0x44,
- 0x02, 0xd3, 0x10, 0x33, 0x02, 0xd4, 0x06, 0x44, 0x00, 0x0c, 0xa3, 0x33,
- 0x38, 0x44, 0x44, 0x4d, 0x16, 0x33, 0x00, 0x08, 0x38, 0x44, 0x44, 0x4c,
- 0x06, 0x33, 0x02, 0x3d, 0x02, 0xc4, 0x1c, 0x44, 0x00, 0x0e, 0xa3, 0x33,
- 0x33, 0x3d, 0x84, 0x44, 0xc3, 0x00, 0x12, 0x33, 0x00, 0x0c, 0xdc, 0x44,
- 0x4a, 0xd3, 0x33, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
- 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
- 0x00, 0x00, 0x00, 0x1e, 0x33, 0x20, 0x00, 0x23, 0x30, 0x00, 0x13, 0x31,
- 0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x26,
- 0x13, 0x31, 0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x00, 0x00, 0x13, 0x33,
- 0x00, 0x00, 0x13, 0x32, 0x00, 0x00, 0x23, 0x00, 0x24, 0x33, 0x00, 0x06,
- 0x32, 0x00, 0x02, 0x00, 0x08, 0x33, 0x02, 0x32, 0x06, 0x00, 0x00, 0x30,
- 0x01, 0x33, 0x31, 0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x33, 0x32, 0x00,
- 0x02, 0x33, 0x33, 0x30, 0x00, 0x13, 0x32, 0x00, 0x02, 0x33, 0x00, 0x01,
- 0x08, 0x33, 0x00, 0x10, 0x30, 0x00, 0x13, 0x31, 0x00, 0x02, 0x33, 0x30,
- 0x08, 0x00, 0x00, 0x10, 0x23, 0x30, 0x00, 0x13, 0x31, 0x00, 0x02, 0x33,
- 0x00, 0x00, 0x00, 0x20, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x10, 0x10, 0x06, 0x00, 0x00, 0x26,
- 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x03, 0x33,
- 0x01, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x00, 0x24, 0x33, 0x00, 0x06,
- 0x32, 0x01, 0x11, 0x00, 0x08, 0x33, 0x00, 0x38, 0x10, 0x10, 0x00, 0x11,
- 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x33, 0x32, 0x01,
- 0x11, 0x33, 0x33, 0x30, 0x11, 0x03, 0x32, 0x01, 0x11, 0x33, 0x01, 0x10,
- 0x08, 0x33, 0x00, 0x12, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00,
- 0x10, 0x00, 0x06, 0x00, 0x00, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31, 0x11,
- 0x02, 0x33, 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11,
- 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00,
- 0x06, 0x33, 0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01,
- 0x11, 0x13, 0x33, 0x01, 0x10, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33,
- 0x00, 0x06, 0x32, 0x01, 0x11, 0x00, 0x08, 0x33, 0x00, 0x38, 0x11, 0x10,
- 0x23, 0x31, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x33,
- 0x32, 0x01, 0x11, 0x33, 0x33, 0x30, 0x11, 0x03, 0x32, 0x01, 0x11, 0x33,
- 0x01, 0x10, 0x08, 0x33, 0x00, 0x12, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02,
- 0x33, 0x01, 0x10, 0x00, 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x23,
- 0x30, 0x11, 0x03, 0x31, 0x11, 0x12, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10,
- 0x23, 0x00, 0x06, 0x33, 0x00, 0x28, 0x31, 0x11, 0x12, 0x33, 0x01, 0x00,
- 0x33, 0x01, 0x11, 0x11, 0x00, 0x01, 0x12, 0x33, 0x32, 0x01, 0x11, 0x11,
- 0x00, 0x23, 0x20, 0x33, 0x00, 0x06, 0x32, 0x01, 0x12, 0x00, 0x08, 0x33,
- 0x00, 0x38, 0x11, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31, 0x11, 0x12, 0x33,
- 0x01, 0x00, 0x33, 0x33, 0x32, 0x01, 0x11, 0x33, 0x33, 0x30, 0x11, 0x13,
- 0x32, 0x01, 0x11, 0x33, 0x01, 0x11, 0x08, 0x33, 0x00, 0x12, 0x30, 0x11,
- 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x00, 0x08, 0x33, 0x00, 0x0e,
- 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x22,
- 0x33, 0x20, 0x11, 0x11, 0x00, 0x11, 0x03, 0x31, 0x11, 0x11, 0x00, 0x01,
- 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33, 0x00, 0x10, 0x31, 0x11,
- 0x11, 0x00, 0x01, 0x13, 0x33, 0x01, 0x08, 0x11, 0x00, 0x10, 0x12, 0x33,
- 0x32, 0x01, 0x11, 0x11, 0x10, 0x23, 0x20, 0x33, 0x00, 0x60, 0x32, 0x01,
- 0x11, 0x10, 0x00, 0x03, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x12, 0x22, 0x01, 0x02, 0x33, 0x33, 0x32, 0x01, 0x11, 0x33, 0x33,
- 0x30, 0x11, 0x12, 0x21, 0x01, 0x11, 0x33, 0x01, 0x11, 0x22, 0x22, 0x23,
- 0x33, 0x30, 0x11, 0x12, 0x21, 0x11, 0x02, 0x33, 0x01, 0x10, 0x08, 0x33,
- 0x00, 0x0e, 0x30, 0x11, 0x12, 0x21, 0x10, 0x13, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x22, 0x33, 0x20, 0x11, 0x00, 0x11, 0x11, 0x03, 0x31, 0x11, 0x00,
- 0x01, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33, 0x00, 0x28,
- 0x31, 0x11, 0x00, 0x01, 0x11, 0x13, 0x33, 0x01, 0x11, 0x00, 0x01, 0x11,
- 0x12, 0x33, 0x32, 0x01, 0x11, 0x00, 0x00, 0x23, 0x20, 0x33, 0x00, 0x60,
- 0x32, 0x01, 0x10, 0x01, 0x11, 0x12, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11,
- 0x03, 0x31, 0x11, 0x10, 0x10, 0x11, 0x13, 0x33, 0x33, 0x32, 0x01, 0x11,
- 0x33, 0x33, 0x30, 0x11, 0x10, 0x10, 0x11, 0x11, 0x33, 0x01, 0x11, 0x01,
- 0x00, 0x01, 0x23, 0x30, 0x11, 0x00, 0x10, 0x11, 0x02, 0x33, 0x01, 0x10,
- 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x00, 0x10, 0x11, 0x23, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x31, 0x11, 0x03, 0x31,
- 0x11, 0x02, 0x33, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33,
- 0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x11, 0x10, 0x33, 0x01, 0x11, 0x03,
- 0x33, 0x11, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33, 0x00, 0x60,
- 0x32, 0x01, 0x11, 0x33, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11,
- 0x03, 0x31, 0x11, 0x01, 0x22, 0x11, 0x12, 0x33, 0x33, 0x32, 0x01, 0x11,
- 0x33, 0x33, 0x30, 0x11, 0x02, 0x22, 0x11, 0x11, 0x33, 0x01, 0x10, 0x22,
- 0x21, 0x11, 0x13, 0x30, 0x11, 0x02, 0x21, 0x11, 0x02, 0x33, 0x01, 0x10,
- 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x02, 0x21, 0x11, 0x23, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33,
- 0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x13,
- 0x33, 0x01, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33, 0x00, 0x60,
- 0x32, 0x01, 0x11, 0x33, 0x01, 0x10, 0x33, 0x11, 0x11, 0x23, 0x30, 0x11,
- 0x03, 0x31, 0x11, 0x12, 0x33, 0x11, 0x10, 0x33, 0x33, 0x32, 0x01, 0x11,
- 0x33, 0x33, 0x30, 0x11, 0x13, 0x32, 0x11, 0x11, 0x33, 0x01, 0x11, 0x33,
- 0x21, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10,
- 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00,
- 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x12, 0x33, 0x01, 0x00, 0x33, 0x11, 0x11, 0x23, 0x00, 0x06, 0x33,
- 0x00, 0x2c, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x11,
- 0x00, 0x01, 0x10, 0x13, 0x32, 0x01, 0x11, 0x11, 0x00, 0x00, 0x01, 0x23,
- 0x1c, 0x33, 0x00, 0x60, 0x32, 0x01, 0x12, 0x33, 0x01, 0x00, 0x33, 0x11,
- 0x11, 0x22, 0x20, 0x11, 0x03, 0x31, 0x11, 0x12, 0x22, 0x01, 0x10, 0x33,
- 0x33, 0x32, 0x01, 0x12, 0x33, 0x33, 0x30, 0x11, 0x12, 0x21, 0x01, 0x11,
- 0x33, 0x01, 0x11, 0x22, 0x10, 0x11, 0x13, 0x30, 0x11, 0x12, 0x21, 0x11,
- 0x02, 0x33, 0x01, 0x11, 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31,
- 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x38, 0x33, 0x20, 0x11, 0x13,
- 0x30, 0x11, 0x03, 0x32, 0x11, 0x11, 0x00, 0x01, 0x02, 0x33, 0x21, 0x11,
- 0x10, 0x00, 0x00, 0x13, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01,
- 0x08, 0x11, 0x00, 0x08, 0x12, 0x33, 0x33, 0x21, 0x08, 0x11, 0x02, 0x10,
- 0x02, 0x23, 0x1c, 0x33, 0x00, 0x0e, 0x32, 0x01, 0x11, 0x10, 0x01, 0x02,
- 0x33, 0x00, 0x06, 0x11, 0x00, 0x62, 0x01, 0x10, 0x03, 0x31, 0x11, 0x11,
- 0x10, 0x11, 0x00, 0x33, 0x10, 0x00, 0x01, 0x11, 0x10, 0x13, 0x30, 0x11,
- 0x11, 0x10, 0x11, 0x01, 0x33, 0x01, 0x11, 0x11, 0x01, 0x10, 0x13, 0x30,
- 0x11, 0x11, 0x00, 0x10, 0x02, 0x33, 0x01, 0x11, 0x10, 0x00, 0x00, 0x23,
- 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12,
- 0x33, 0x20, 0x00, 0x13, 0x31, 0x00, 0x03, 0x33, 0x20, 0x00, 0x06, 0x00,
- 0x00, 0x06, 0x13, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x12, 0x03, 0x32,
- 0x00, 0x02, 0x33, 0x10, 0x00, 0x33, 0x10, 0x00, 0x08, 0x00, 0x00, 0x08,
- 0x02, 0x33, 0x33, 0x30, 0x0a, 0x00, 0x02, 0x23, 0x1c, 0x33, 0x02, 0x32,
- 0x08, 0x00, 0x00, 0x06, 0x03, 0x33, 0x32, 0x00, 0x06, 0x00, 0x00, 0x06,
- 0x01, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x06, 0x13, 0x33, 0x20, 0x00,
- 0x08, 0x00, 0x00, 0x06, 0x03, 0x33, 0x10, 0x00, 0x06, 0x00, 0x00, 0x06,
- 0x23, 0x33, 0x10, 0x00, 0x06, 0x00, 0x00, 0x06, 0x02, 0x33, 0x33, 0x00,
- 0x08, 0x00, 0x00, 0x06, 0x23, 0x33, 0x30, 0x00, 0x08, 0x00, 0x00, 0x10,
- 0x23, 0x31, 0x00, 0x03, 0x32, 0x00, 0x02, 0x33, 0x00, 0x00, 0xe6, 0x33,
- 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
- 0x00, 0x01};
-unsigned int splash_new__new_t_len = 3854;
diff --git a/firmware/application/database.cpp b/firmware/application/database.cpp
index 432453b9f..5905277e0 100644
--- a/firmware/application/database.cpp
+++ b/firmware/application/database.cpp
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
+ * Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
return (result);
}
+int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
+ file_path = macaddress_dir / u"macaddress.db";
+ index_item_length = 7;
+ record_length = 64;
+
+ result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
+
+ return (result);
+}
+
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
diff --git a/firmware/application/database.hpp b/firmware/application/database.hpp
index 4720da87a..862fa692d 100644
--- a/firmware/application/database.hpp
+++ b/firmware/application/database.hpp
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
+ * Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -60,6 +61,12 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
+ struct MacAddressDBRecord {
+ char vendor_name[64]; // vendor/manufacturer name
+ };
+
+ int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
+
private:
std::filesystem::path file_path = ""; // path including filename
int index_item_length = 0; // length of index item
@@ -77,4 +84,4 @@ class database {
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
-#endif /*__DATABASE_H__*/
+#endif /*__DATABASE_H__*/
\ No newline at end of file
diff --git a/firmware/application/de_bruijn.cpp b/firmware/application/de_bruijn.cpp
deleted file mode 100644
index 97ae105ed..000000000
--- a/firmware/application/de_bruijn.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2017 Furrtek
- *
- * 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 "de_bruijn.hpp"
-
-/* How this works:
- * B(2,4) means:
- * Alphabet size = 2 (binary)
- * Code length = 4
- * The length of the bitstream is (2 ^ 4) + (4 - 1) = 19 bits
- * The primitive polynomials come from the de_bruijn_polys[] array (one for each code length)
- * The shift register is init with 1 and shifted left each step
- * The polynomial is kept on the right, and used as a AND mask applied on the corresponding shift register bits
- * The resulting bits are XORed together to produce the new bit pushed in the shift register
- *
- * 0001 (init)
- * AND 1001 (polynomial)
- * 0001 XOR'd -> 1
- *
- * 00011 (shift left)
- * AND 1001
- * 0001 XOR'd -> 1
- *
- * 000111 (shift left)
- * AND 1001
- * 0001 XOR'd -> 1
- *
- * 0001111 (shift left)
- * AND 1001
- * 1001 XOR'd -> 0
- *
- * 00011110 (shift left)
- * AND 1001
- * 1000 XOR'd -> 1
- * ...
- * After 16 steps: (0+) 000111101011001000
- * Each of the 16 possible values appears in the sequence:
- * -0000-111101011001000 0
- * 0-0001-11101011001000 1
- * 0000111101011-0010-00 2
- * 00-0011-1101011001000 3
- * 00001111010110-0100-0 4
- * 00001111-0101-1001000 5
- * 0000111101-0110-01000 6
- * 000-0111-101011001000 7
- * 000011110101100-1000- 8
- * 000011110101-1001-000 9
- * 0000111-1010-11001000 10
- * 000011110-1011-001000 11
- * 00001111010-1100-1000 12
- * 000011-1101-011001000 13
- * 00001-1110-1011001000 14
- * 0000-1111-01011001000 15
- */
-
-size_t de_bruijn::init(const uint32_t n) {
- // Cap
- if ((n < 3) || (n > 16))
- length = 3;
- else
- length = n;
-
- poly = de_bruijn_polys[length - 3];
- shift_register = 1;
-
- return (1U << length) + (length - 1);
-}
-
-uint32_t de_bruijn::compute(const uint32_t steps) {
- uint32_t step, bits, masked;
- uint8_t new_bit;
-
- for (step = 0; step < steps; step++) {
- masked = shift_register & poly;
- new_bit = 0;
- for (bits = 0; bits < length; bits++) {
- new_bit ^= (masked & 1);
- masked >>= 1;
- }
- shift_register <<= 1;
- shift_register |= new_bit;
- }
-
- return shift_register;
-}
diff --git a/firmware/application/de_bruijn.hpp b/firmware/application/de_bruijn.hpp
deleted file mode 100644
index cc4c5b3e8..000000000
--- a/firmware/application/de_bruijn.hpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2017 Furrtek
- *
- * 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
-#include
-
-#ifndef __DE_BRUIJN_H__
-#define __DE_BRUIJN_H__
-
-// n from 3 to 16
-const uint32_t de_bruijn_polys[14]{
- 0b0000000000000101,
- 0b0000000000001001,
- 0b0000000000011011,
- 0b0000000000110011,
- 0b0000000001010011,
- 0b0000000010001101,
- 0b0000000100100001,
- 0b0000001000100001,
- 0b0000010001100001,
- 0b0000110101000011,
- 0b0001001001100101,
- 0b0010101100000011,
- 0b0101000000001001,
- 0b1010000101000101};
-
-struct de_bruijn {
- public:
- size_t init(const uint32_t n);
- uint32_t compute(const uint32_t steps);
-
- private:
- uint32_t length{};
- uint32_t poly{};
- uint32_t shift_register{};
-};
-
-#endif /*__DE_BRUIJN_H__*/
diff --git a/firmware/application/external/acars_rx/acars_app.hpp b/firmware/application/external/acars_rx/acars_app.hpp
index ac99e7045..abefedce0 100644
--- a/firmware/application/external/acars_rx/acars_app.hpp
+++ b/firmware/application/external/acars_rx/acars_app.hpp
@@ -87,10 +87,10 @@ class ACARSAppView : public View {
true};
Console console{
- {0, 3 * 16, 240, 256}};
+ {0, 3 * 16, screen_width, 256}};
AudioVolumeField field_volume{
- {28 * 8, 1 * 16}};
+ {screen_width - 2 * 8, 1 * 16}};
std::unique_ptr logger{};
diff --git a/firmware/application/external/adsbtx/ui_adsb_tx.cpp b/firmware/application/external/adsbtx/ui_adsb_tx.cpp
index 380b80821..4fdece1ae 100644
--- a/firmware/application/external/adsbtx/ui_adsb_tx.cpp
+++ b/firmware/application/external/adsbtx/ui_adsb_tx.cpp
@@ -131,6 +131,7 @@ ADSBCallsignView::ADSBCallsignView(
nav,
callsign,
8,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
button_callsign.set_text(s);
});
diff --git a/firmware/application/external/adsbtx/ui_adsb_tx.hpp b/firmware/application/external/adsbtx/ui_adsb_tx.hpp
index d39af7809..245ffd43e 100644
--- a/firmware/application/external/adsbtx/ui_adsb_tx.hpp
+++ b/firmware/application/external/adsbtx/ui_adsb_tx.hpp
@@ -218,7 +218,7 @@ class ADSBTxView : public View {
void start_tx();
void generate_frames();
- Rect view_rect = {0, 7 * 8, 240, 192};
+ Rect view_rect = {0, 7 * 8, screen_width, 192};
ADSBPositionView view_position{nav_, view_rect};
ADSBCallsignView view_callsign{nav_, view_rect};
diff --git a/firmware/application/external/afsk_rx/ui_afsk_rx.hpp b/firmware/application/external/afsk_rx/ui_afsk_rx.hpp
index 2ebcd08ea..388a966c7 100644
--- a/firmware/application/external/afsk_rx/ui_afsk_rx.hpp
+++ b/firmware/application/external/afsk_rx/ui_afsk_rx.hpp
@@ -84,7 +84,7 @@ class AFSKRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -105,7 +105,7 @@ class AFSKRxView : public View {
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
- {0, 4 * 16, 240, screen_width}};
+ {0, 4 * 16, screen_width, screen_width}};
void on_data_afsk(const AFSKDataMessage& message);
diff --git a/firmware/application/external/analogtv/analog_tv_app.hpp b/firmware/application/external/analogtv/analog_tv_app.hpp
index 21445fb09..04aadd52a 100644
--- a/firmware/application/external/analogtv/analog_tv_app.hpp
+++ b/firmware/application/external/analogtv/analog_tv_app.hpp
@@ -58,7 +58,7 @@ class AnalogTvView : public View {
app_settings::SettingsManager settings_{
"rx_tv", app_settings::Mode::RX};
- const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
+ const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
RSSI rssi{
diff --git a/firmware/application/external/antenna_length/main.cpp b/firmware/application/external/antenna_length/main.cpp
new file mode 100644
index 000000000..97dfa8644
--- /dev/null
+++ b/firmware/application/external/antenna_length/main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_whipcalc.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::antenna_length {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::antenna_length
+
+extern "C" {
+
+__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Antenna Length",
+ /*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 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
+};
+}
diff --git a/firmware/application/apps/ui_whipcalc.cpp b/firmware/application/external/antenna_length/ui_whipcalc.cpp
similarity index 98%
rename from firmware/application/apps/ui_whipcalc.cpp
rename to firmware/application/external/antenna_length/ui_whipcalc.cpp
index 8285e66fc..72604f1c5 100644
--- a/firmware/application/apps/ui_whipcalc.cpp
+++ b/firmware/application/external/antenna_length/ui_whipcalc.cpp
@@ -32,8 +32,9 @@
#include
using namespace portapack;
+using namespace ui;
-namespace ui {
+namespace ui::external_app::antenna_length {
void WhipCalcView::focus() {
field_frequency.focus();
@@ -176,4 +177,4 @@ void WhipCalcView::load_antenna_db() {
void WhipCalcView::add_default_antenna() {
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
}
-} // namespace ui
+} // namespace ui::external_app::antenna_length
diff --git a/firmware/application/apps/ui_whipcalc.hpp b/firmware/application/external/antenna_length/ui_whipcalc.hpp
similarity index 94%
rename from firmware/application/apps/ui_whipcalc.hpp
rename to firmware/application/external/antenna_length/ui_whipcalc.hpp
index 0f807c5f7..78fa3f56a 100644
--- a/firmware/application/apps/ui_whipcalc.hpp
+++ b/firmware/application/external/antenna_length/ui_whipcalc.hpp
@@ -31,7 +31,10 @@
#include "string_format.hpp"
#include
-namespace ui {
+using namespace ui;
+
+namespace ui::external_app::antenna_length {
+
class WhipCalcView : public View {
public:
WhipCalcView(NavigationView& nav);
@@ -83,13 +86,13 @@ class WhipCalcView : public View {
{13 * 8, 4 * 16, 10 * 16, 16},
"-"};
Console console{
- {0, 6 * 16, 240, 160}};
+ {0, 6 * 16, screen_width, 160}};
Button button_exit{
{72, 17 * 16, 96, 32},
"Back"};
};
-} /* namespace ui */
+} // namespace ui::external_app::antenna_length
#endif /*__UI_WHIPCALC__*/
diff --git a/firmware/application/external/app_manager/main.cpp b/firmware/application/external/app_manager/main.cpp
new file mode 100644
index 000000000..ccb9b38bc
--- /dev/null
+++ b/firmware/application/external/app_manager/main.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_app_manager.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::app_manager {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::app_manager
+
+extern "C" {
+
+__attribute__((section(".external_app.app_app_manager.application_information"), used)) application_information_t _application_information_app_manager = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::app_manager::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "AppManager",
+ /*.bitmap_data = */ {
+ 0xE0,
+ 0x00,
+ 0x10,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x1F,
+ 0x1F,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x10,
+ 0x01,
+ 0x70,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0x70,
+ 0xE1,
+ 0x10,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x1F,
+ 0x1F,
+
+ },
+ /*.icon_color = */ ui::Color::cyan().v,
+ /*.menu_location = */ app_location_t::SETTINGS,
+ /*.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/app_manager/ui_app_manager.cpp b/firmware/application/external/app_manager/ui_app_manager.cpp
new file mode 100644
index 000000000..d7ebbfc1e
--- /dev/null
+++ b/firmware/application/external/app_manager/ui_app_manager.cpp
@@ -0,0 +1,275 @@
+/*
+ * copyleft spammingdramaqueen
+ *
+ * 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_app_manager.hpp"
+#include "ui_navigation.hpp"
+#include "ui_external_items_menu_loader.hpp"
+
+#include "file.hpp"
+namespace fs = std::filesystem;
+
+#include "string_format.hpp"
+
+#include "file_reader.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::app_manager {
+
+/* AppManagerView **************************************/
+/* | inner apps | external apps |
+ * ------------------------------
+ * | id | appCallName |
+ * | displayName|appFriendlyName|
+ */
+AppManagerView::AppManagerView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&labels,
+ &menu_view,
+ &text_app_info,
+ &button_hide_unhide,
+ &button_clean_hide,
+ &button_set_cancel_autostart,
+ &button_clean_autostart});
+
+ menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
+
+ menu_view.on_highlight = [this]() {
+ if (menu_view.highlighted_index() >= app_list_index) {
+ text_app_info.set("");
+ return;
+ }
+
+ std::string info;
+ auto app_name = get_app_info(menu_view.highlighted_index(), true);
+ auto app_id = get_app_info(menu_view.highlighted_index(), false);
+
+ info += "Hidden:";
+
+ if (is_blacklisted(app_name)) {
+ info += "Yes ";
+ } else {
+ info += "No ";
+ }
+
+ info += "Autostart:";
+ if (is_autostart_app(app_id)) {
+ info += "Yes";
+ } else {
+ info += "No";
+ }
+
+ if (info.empty()) {
+ info = "Highlight an app";
+ }
+
+ text_app_info.set(info);
+ };
+
+ button_hide_unhide.on_select = [this](Button&) {
+ hide_unhide_app();
+ refresh_list();
+ };
+
+ button_clean_hide.on_select = [this](Button&) {
+ clean_blacklist();
+ refresh_list();
+ };
+
+ button_set_cancel_autostart.on_select = [this](Button&) {
+ set_unset_autostart_app();
+ refresh_list();
+ };
+
+ button_clean_autostart.on_select = [this](Button&) {
+ unset_auto_start();
+ refresh_list();
+ };
+
+ refresh_list();
+}
+
+void AppManagerView::refresh_list() {
+ auto previous_cursor_index = menu_view.highlighted_index();
+ app_list_index = 0;
+ menu_view.clear();
+
+ size_t index = 0;
+
+ auto padding = [](const std::string& str) {
+ return str.length() < 25 ? std::string(25 - str.length(), ' ') + '' : "";
+ // that weird char is a icon in portapack's font base that looks like a switch, which i use to indicate the auto start
+ };
+
+ for (auto& app : NavigationView::appList) {
+ if (app.id == nullptr) continue;
+
+ app_list_index++;
+
+ menu_view.add_item({app.displayName + std::string(is_autostart_app(app.id) ? padding(app.displayName) : ""),
+ app.iconColor,
+ app.icon,
+ [this, app_id = std::string(app.id)](KeyEvent) {
+ button_hide_unhide.focus();
+ }});
+ }
+
+ ExternalItemsMenuLoader::load_all_external_items_callback([this, &index, &padding](ui::AppInfoConsole& app) {
+ if (app.appCallName == nullptr) return;
+
+ app_list_index++;
+
+ menu_view.add_item({app.appFriendlyName + std::string(is_autostart_app(app.appCallName) ? padding(app.appFriendlyName) : ""),
+ ui::Color::light_grey(),
+ &bitmap_icon_sdcard,
+ [this, app_id = std::string(app.appCallName)](KeyEvent) {
+ button_hide_unhide.focus();
+ }});
+ });
+
+ menu_view.set_highlighted(previous_cursor_index);
+}
+
+void AppManagerView::focus() {
+ menu_view.focus();
+}
+
+void AppManagerView::hide_app() {
+ std::vector blacklist;
+ get_blacklist(blacklist);
+
+ blacklist.push_back(get_app_info(menu_view.highlighted_index(), true));
+ write_blacklist(blacklist);
+}
+
+void AppManagerView::unhide_app() {
+ std::vector blacklist;
+ get_blacklist(blacklist);
+ blacklist.erase(std::remove(blacklist.begin(), blacklist.end(), get_app_info(menu_view.highlighted_index(), true)), blacklist.end());
+ write_blacklist(blacklist);
+}
+
+void AppManagerView::hide_unhide_app() {
+ if (is_blacklisted(get_app_info(menu_view.highlighted_index(), true)))
+ unhide_app();
+ else
+ hide_app();
+}
+
+void AppManagerView::get_blacklist(std::vector& blacklist) {
+ File f;
+
+ auto error = f.open(u"SETTINGS/blacklist");
+ if (error)
+ return;
+
+ auto reader = FileLineReader(f);
+ for (const auto& line : reader) {
+ if (line.length() == 0 || line[0] == '#')
+ continue;
+
+ blacklist.push_back(trim(line));
+ }
+}
+
+void AppManagerView::write_blacklist(const std::vector& blacklist) {
+ File f;
+ auto error = f.create(u"SETTINGS/blacklist");
+ if (error)
+ return;
+
+ for (const auto& app_name : blacklist) {
+ auto line = app_name + "\n";
+ f.write(line.c_str(), line.length());
+ }
+}
+
+void AppManagerView::clean_blacklist() {
+ std::vector blacklist = {};
+ write_blacklist(blacklist);
+}
+
+bool AppManagerView::is_blacklisted(const std::string& app_name) {
+ std::vector blacklist;
+ get_blacklist(blacklist);
+ return std::find(blacklist.begin(), blacklist.end(), app_name) != blacklist.end();
+}
+
+void AppManagerView::set_auto_start() {
+ auto app_index = menu_view.highlighted_index();
+ if (app_index >= app_list_index) return;
+
+ auto id_aka_app_call_name = get_app_info(app_index, false);
+
+ autostart_app = id_aka_app_call_name;
+
+ refresh_list();
+}
+
+void AppManagerView::unset_auto_start() {
+ autostart_app = "";
+ refresh_list();
+}
+
+void AppManagerView::set_unset_autostart_app() {
+ auto app_index = menu_view.highlighted_index();
+ if (app_index >= app_list_index) return;
+
+ auto id_aka_friendly_name = get_app_info(app_index, false);
+
+ if (is_autostart_app(id_aka_friendly_name))
+ unset_auto_start();
+ else
+ set_auto_start();
+}
+
+bool AppManagerView::is_autostart_app(const char* id_aka_friendly_name) {
+ return autostart_app == std::string(id_aka_friendly_name);
+}
+
+bool AppManagerView::is_autostart_app(const std::string& id_aka_friendly_name) {
+ return autostart_app == id_aka_friendly_name;
+}
+
+std::string AppManagerView::get_app_info(uint16_t index, bool is_display_name) {
+ size_t current_index = 0;
+ std::string result;
+
+ for (auto& app : NavigationView::appList) {
+ if (app.id == nullptr) continue;
+ if (current_index == index) {
+ return is_display_name ? std::string(app.displayName) : std::string(app.id);
+ }
+ current_index++;
+ }
+
+ ExternalItemsMenuLoader::load_all_external_items_callback([¤t_index, &index, &result, &is_display_name](ui::AppInfoConsole& app) {
+ if (app.appCallName == nullptr) return;
+ if (current_index == index) {
+ result = is_display_name ? app.appFriendlyName : app.appCallName;
+ }
+ current_index++;
+ });
+
+ return result;
+}
+
+} // namespace ui::external_app::app_manager
\ No newline at end of file
diff --git a/firmware/application/external/app_manager/ui_app_manager.hpp b/firmware/application/external/app_manager/ui_app_manager.hpp
new file mode 100644
index 000000000..5c2f5597e
--- /dev/null
+++ b/firmware/application/external/app_manager/ui_app_manager.hpp
@@ -0,0 +1,87 @@
+/*
+ * copyleft spammingdramaqueen
+ *
+ * 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_APP_MANAGER_H__
+#define __UI_APP_MANAGER_H__
+
+#include "ui_navigation.hpp"
+
+namespace ui::external_app::app_manager {
+
+class AppManagerView : public View {
+ public:
+ AppManagerView(NavigationView& nav);
+ std::string title() const override { return "AppMan"; };
+
+ private:
+ NavigationView& nav_;
+ std::string autostart_app{""};
+ SettingsStore nav_setting{
+ "nav"sv,
+ {{"autostart_app"sv, &autostart_app}}};
+
+ void focus() override;
+ void refresh_list();
+ uint16_t app_list_index{0};
+
+ Labels labels{
+ {{0 * 8, 0 * 16}, "App list:", Theme::getInstance()->fg_light->foreground}};
+
+ MenuView menu_view{};
+
+ Text text_app_info{
+ {0, 27 * 8, screen_width, 16},
+ "Highlight an app"};
+
+ Button button_hide_unhide{
+ {0, 29 * 8, screen_width / 2 - 1, 32},
+ "Hide/Show"};
+
+ Button button_clean_hide{
+ {screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
+ "Clean Hidden"};
+
+ Button button_set_cancel_autostart{
+ {0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
+ "Set Autostart"};
+
+ Button button_clean_autostart{
+ {screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
+ "Del Autostart"};
+
+ std::string get_app_info(uint16_t index, bool is_display_name);
+ void get_blacklist(std::vector& blacklist);
+ void write_blacklist(const std::vector& blacklist);
+ bool is_blacklisted(const std::string& app_display_name);
+ void hide_app();
+ void unhide_app();
+ void hide_unhide_app();
+ void clean_blacklist();
+ bool is_autostart_app(const std::string& display_name);
+ void set_auto_start();
+ void unset_auto_start();
+ void set_unset_autostart_app();
+ bool is_autostart_app(const char* id_aka_app_call_name);
+};
+
+} // namespace ui::external_app::app_manager
+
+#endif // __UI_APP_MANAGER_H__
\ No newline at end of file
diff --git a/firmware/application/external/battleship/main.cpp b/firmware/application/external/battleship/main.cpp
new file mode 100644
index 000000000..3ba40f401
--- /dev/null
+++ b/firmware/application/external/battleship/main.cpp
@@ -0,0 +1,55 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui.hpp"
+#include "ui_battleship.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::battleship {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::battleship
+
+extern "C" {
+
+__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Battleship",
+ /*.bitmap_data = */ {
+ // Pirate galleon - 16x16
+ 0x80, 0x00, // ........#.......
+ 0x80, 0x00, // ........#.......
+ 0x80, 0x00, // ........#.......
+ 0xC0, 0x01, // .......###......
+ 0xE0, 0x03, // ......#####.....
+ 0xF0, 0x07, // .....#######....
+ 0xF8, 0x0F, // ....#########...
+ 0xFC, 0x1F, // ...###########..
+ 0xFE, 0x3F, // ..#############.
+ 0x00, 0x01, // .......#........
+ 0x00, 0x01, // .......#........
+ 0x00, 0x01, // .......#........
+ 0xFC, 0x3F, // ..############..
+ 0xFE, 0x7F, // .##############.
+ 0xFF, 0xFF, // ################
+ 0xFC, 0x3F, // ..############..
+ },
+ /*.icon_color = */ ui::Color::blue().v,
+ /*.menu_location = */ app_location_t::GAMES,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/battleship/ui_battleship.cpp b/firmware/application/external/battleship/ui_battleship.cpp
new file mode 100644
index 000000000..693fa200a
--- /dev/null
+++ b/firmware/application/external/battleship/ui_battleship.cpp
@@ -0,0 +1,1034 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_battleship.hpp"
+#include "portapack_shared_memory.hpp"
+#include "utility.hpp"
+#include "modems.hpp"
+#include "bch_code.hpp"
+
+using namespace portapack;
+using namespace modems;
+
+namespace ui::external_app::battleship {
+
+// POCSAG address for battleship game messages
+constexpr uint32_t BATTLESHIP_BASE_ADDRESS = 1000000;
+constexpr uint32_t RED_TEAM_ADDRESS = BATTLESHIP_BASE_ADDRESS + 1;
+constexpr uint32_t BLUE_TEAM_ADDRESS = BATTLESHIP_BASE_ADDRESS + 2;
+
+BattleshipView::BattleshipView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
+
+ add_children({&text_title, &text_subtitle,
+ &rect_radio_settings, &label_radio, &button_frequency,
+ &label_rf_amp, &checkbox_rf_amp,
+ &label_lna, &field_lna,
+ &label_vga, &field_vga,
+ &label_tx_gain, &field_tx_gain,
+ &rect_audio_settings, &label_audio,
+ &checkbox_sound, &label_volume, &field_volume,
+ &rect_team_selection, &label_team,
+ &button_red_team, &button_blue_team,
+ &rssi, &text_status, &text_score,
+ &button_rotate, &button_place, &button_fire, &button_menu});
+
+ // Hide in-game elements
+ rssi.hidden(true);
+ text_status.hidden(true);
+ text_score.hidden(true);
+ button_rotate.hidden(true);
+ button_place.hidden(true);
+ button_fire.hidden(true);
+ button_menu.hidden(true);
+
+ // Configure frequency button
+ button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
+
+ button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
+ auto new_view = nav_.push(tx_frequency);
+ new_view->on_changed = [this, &button](rf::Frequency f) {
+ tx_frequency = f;
+ rx_frequency = f;
+ button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
+ if (!is_transmitting) {
+ receiver_model.set_target_frequency(rx_frequency);
+ }
+ };
+ };
+
+ button_frequency.on_change = [this]() {
+ int64_t def_step = 25000;
+ int64_t new_freq = static_cast(tx_frequency) + (button_frequency.get_encoder_delta() * def_step);
+
+ if (new_freq < 1) new_freq = 1;
+ if (new_freq > 7200000000LL) new_freq = 7200000000LL;
+
+ tx_frequency = static_cast(new_freq);
+ rx_frequency = tx_frequency;
+ button_frequency.set_encoder_delta(0);
+ button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
+ if (!is_transmitting) {
+ receiver_model.set_target_frequency(rx_frequency);
+ }
+ };
+
+ // Radio controls
+ checkbox_rf_amp.set_value(rf_amp_enabled);
+ checkbox_rf_amp.on_select = [this](Checkbox&, bool v) {
+ rf_amp_enabled = v;
+ transmitter_model.set_rf_amp(v);
+ receiver_model.set_rf_amp(v);
+ };
+
+ field_lna.set_value(lna_gain);
+ field_lna.on_change = [this](int32_t v) {
+ lna_gain = v;
+ receiver_model.set_lna(v);
+ };
+
+ field_vga.set_value(vga_gain);
+ field_vga.on_change = [this](int32_t v) {
+ vga_gain = v;
+ receiver_model.set_vga(v);
+ };
+
+ field_tx_gain.set_value(tx_gain);
+ field_tx_gain.on_change = [this](int32_t v) {
+ tx_gain = v;
+ transmitter_model.set_tx_gain(v);
+ };
+
+ // Audio controls
+ checkbox_sound.set_value(sound_enabled);
+ checkbox_sound.on_select = [this](Checkbox&, bool v) {
+ sound_enabled = v;
+ if (sound_enabled) {
+ audio::output::unmute();
+ } else {
+ audio::output::mute();
+ }
+ };
+
+ // Team selection
+ button_red_team.on_select = [this](Button&) {
+ start_team(true);
+ };
+
+ button_blue_team.on_select = [this](Button&) {
+ start_team(false);
+ };
+
+ // In-game controls
+ button_rotate.on_select = [this](Button&) {
+ placing_horizontal = !placing_horizontal;
+ set_dirty();
+ };
+
+ button_place.on_select = [this](Button&) {
+ place_ship();
+ };
+
+ button_fire.on_select = [this](Button&) {
+ fire_at_position();
+ };
+
+ button_menu.on_select = [this](Button&) {
+ reset_game();
+ };
+
+ // Set proper rectangles for layout
+ button_frequency.set_parent_rect({17, 65, 11 * 8, 20});
+ checkbox_rf_amp.set_parent_rect({55, 90, 24, 24});
+ field_lna.set_parent_rect({50, 118, 32, 16});
+ field_vga.set_parent_rect({125, 118, 32, 16});
+ field_tx_gain.set_parent_rect({185, 118, 32, 16});
+ checkbox_sound.set_parent_rect({17, 187, 80, 24});
+ field_volume.set_parent_rect({165, 187, 32, 16});
+ button_red_team.set_parent_rect({25, 242, 85, 45});
+ button_blue_team.set_parent_rect({130, 242, 85, 45});
+
+ // Make menu elements focusable
+ button_frequency.set_focusable(true);
+ checkbox_rf_amp.set_focusable(true);
+ field_lna.set_focusable(true);
+ field_vga.set_focusable(true);
+ field_tx_gain.set_focusable(true);
+ checkbox_sound.set_focusable(true);
+ field_volume.set_focusable(true);
+ button_red_team.set_focusable(true);
+ button_blue_team.set_focusable(true);
+
+ set_focusable(true);
+ init_game();
+}
+
+BattleshipView::~BattleshipView() {
+ transmitter_model.disable();
+ receiver_model.disable();
+ audio::output::stop();
+ baseband::shutdown();
+}
+
+void BattleshipView::focus() {
+ if (game_state == GameState::MENU) {
+ button_frequency.focus();
+ } else {
+ View::focus();
+ }
+}
+
+void BattleshipView::init_game() {
+ for (uint8_t y = 0; y < GRID_SIZE; y++) {
+ for (uint8_t x = 0; x < GRID_SIZE; x++) {
+ my_grid[y][x] = CellState::EMPTY;
+ enemy_grid[y][x] = CellState::EMPTY;
+ }
+ }
+
+ setup_ships();
+ update_score();
+}
+
+void BattleshipView::reset_game() {
+ transmitter_model.disable();
+ receiver_model.disable();
+ audio::output::stop();
+
+ game_state = GameState::MENU;
+ is_red_team = false;
+ opponent_ready = false;
+ current_ship_index = 0;
+ placing_horizontal = true;
+ ships_remaining = 5;
+ enemy_ships_remaining = 5;
+ cursor_x = 0;
+ cursor_y = 0;
+ target_x = 0;
+ target_y = 0;
+ is_transmitting = false;
+ last_address = 0;
+
+ init_game();
+
+ current_status = "Choose your team!";
+ update_score();
+
+ // Toggle visibility
+ bool menu_vis = true;
+ bool game_vis = false;
+
+ text_title.hidden(!menu_vis);
+ text_subtitle.hidden(!menu_vis);
+ rect_radio_settings.hidden(!menu_vis);
+ label_radio.hidden(!menu_vis);
+ button_frequency.hidden(!menu_vis);
+ label_rf_amp.hidden(!menu_vis);
+ checkbox_rf_amp.hidden(!menu_vis);
+ label_lna.hidden(!menu_vis);
+ field_lna.hidden(!menu_vis);
+ label_vga.hidden(!menu_vis);
+ field_vga.hidden(!menu_vis);
+ label_tx_gain.hidden(!menu_vis);
+ field_tx_gain.hidden(!menu_vis);
+ rect_audio_settings.hidden(!menu_vis);
+ label_audio.hidden(!menu_vis);
+ checkbox_sound.hidden(!menu_vis);
+ label_volume.hidden(!menu_vis);
+ field_volume.hidden(!menu_vis);
+ rect_team_selection.hidden(!menu_vis);
+ label_team.hidden(!menu_vis);
+ button_red_team.hidden(!menu_vis);
+ button_blue_team.hidden(!menu_vis);
+
+ rssi.hidden(!game_vis);
+ text_status.hidden(!game_vis);
+ text_score.hidden(!game_vis);
+ button_rotate.hidden(!game_vis);
+ button_place.hidden(!game_vis);
+ button_fire.hidden(!game_vis);
+ button_menu.hidden(!game_vis);
+
+ // Restore focusability
+ button_frequency.set_focusable(menu_vis);
+ checkbox_rf_amp.set_focusable(menu_vis);
+ field_lna.set_focusable(menu_vis);
+ field_vga.set_focusable(menu_vis);
+ field_tx_gain.set_focusable(menu_vis);
+ checkbox_sound.set_focusable(menu_vis);
+ field_volume.set_focusable(menu_vis);
+ button_red_team.set_focusable(menu_vis);
+ button_blue_team.set_focusable(menu_vis);
+
+ button_frequency.focus();
+ set_dirty();
+}
+
+void BattleshipView::setup_ships() {
+ static const ShipType types[] = {ShipType::CARRIER, ShipType::BATTLESHIP,
+ ShipType::CRUISER, ShipType::SUBMARINE, ShipType::DESTROYER};
+ for (uint8_t i = 0; i < 5; i++) {
+ my_ships[i] = {types[i], 0, 0, true, 0, false};
+ }
+}
+
+void BattleshipView::start_team(bool red) {
+ is_red_team = red;
+ game_state = GameState::PLACING_SHIPS;
+
+ // Toggle visibility
+ bool menu_vis = false;
+ bool game_vis = true;
+
+ text_title.hidden(!menu_vis);
+ text_subtitle.hidden(!menu_vis);
+ rect_radio_settings.hidden(!menu_vis);
+ label_radio.hidden(!menu_vis);
+ button_frequency.hidden(!menu_vis);
+ label_rf_amp.hidden(!menu_vis);
+ checkbox_rf_amp.hidden(!menu_vis);
+ label_lna.hidden(!menu_vis);
+ field_lna.hidden(!menu_vis);
+ label_vga.hidden(!menu_vis);
+ field_vga.hidden(!menu_vis);
+ label_tx_gain.hidden(!menu_vis);
+ field_tx_gain.hidden(!menu_vis);
+ rect_audio_settings.hidden(!menu_vis);
+ label_audio.hidden(!menu_vis);
+ checkbox_sound.hidden(!menu_vis);
+ label_volume.hidden(!menu_vis);
+ field_volume.hidden(!menu_vis);
+ rect_team_selection.hidden(!menu_vis);
+ label_team.hidden(!menu_vis);
+ button_red_team.hidden(!menu_vis);
+ button_blue_team.hidden(!menu_vis);
+
+ rssi.hidden(!game_vis);
+ text_status.hidden(true);
+ text_score.hidden(true);
+ button_rotate.hidden(false);
+ button_place.hidden(false);
+ button_menu.hidden(false);
+
+ current_status = "Place carrier (5)";
+
+ button_rotate.set_focusable(false);
+ button_place.set_focusable(false);
+ button_menu.set_focusable(false);
+
+ focus();
+ is_transmitting = true;
+ configure_radio_rx();
+ set_dirty();
+}
+
+void BattleshipView::configure_radio_tx() {
+ if (is_transmitting) return;
+
+ audio::output::stop();
+ receiver_model.disable();
+ baseband::shutdown();
+
+ chThdSleepMilliseconds(100);
+
+ baseband::run_image(portapack::spi_flash::image_tag_fsktx);
+
+ chThdSleepMilliseconds(100);
+
+ transmitter_model.set_target_frequency(tx_frequency);
+ transmitter_model.set_sampling_rate(2280000);
+ transmitter_model.set_baseband_bandwidth(1750000);
+ transmitter_model.set_rf_amp(rf_amp_enabled);
+ transmitter_model.set_tx_gain(tx_gain);
+
+ is_transmitting = true;
+}
+
+void BattleshipView::configure_radio_rx() {
+ if (is_transmitting) {
+ transmitter_model.disable();
+ baseband::shutdown();
+ chThdSleepMilliseconds(100);
+ }
+
+ baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
+ chThdSleepMilliseconds(100);
+
+ receiver_model.set_target_frequency(rx_frequency);
+ receiver_model.set_sampling_rate(3072000);
+ receiver_model.set_baseband_bandwidth(1750000);
+ receiver_model.set_rf_amp(rf_amp_enabled);
+ receiver_model.set_lna(lna_gain);
+ receiver_model.set_vga(vga_gain);
+
+ baseband::set_pocsag();
+ receiver_model.enable();
+
+ audio::set_rate(audio::Rate::Hz_24000);
+
+ if (sound_enabled) {
+ audio::output::start();
+ }
+
+ is_transmitting = false;
+ current_status = "RX Ready";
+ set_dirty();
+}
+
+void BattleshipView::paint(Painter& painter) {
+ painter.fill_rectangle({0, 0, 240, 320}, Color::black());
+
+ if (game_state == GameState::MENU) {
+ draw_menu_screen(painter);
+
+ // Custom paint team buttons
+ if (!button_red_team.hidden()) {
+ Rect r = button_red_team.screen_rect();
+ painter.fill_rectangle(r, Color::dark_red());
+ painter.draw_rectangle(r, Color::red());
+
+ if (button_red_team.has_focus()) {
+ painter.draw_rectangle({r.location().x() - 1, r.location().y() - 1, r.size().width() + 2, r.size().height() + 2}, Color::yellow());
+ }
+
+ auto style_white = Style{
+ .font = ui::font::fixed_8x16,
+ .background = Color::dark_red(),
+ .foreground = Color::white()};
+ painter.draw_string(r.center() - Point(24, 16), style_white, "RED");
+ painter.draw_string(r.center() - Point(24, 0), style_white, "TEAM");
+ }
+
+ if (!button_blue_team.hidden()) {
+ Rect r = button_blue_team.screen_rect();
+ painter.fill_rectangle(r, Color::dark_blue());
+ painter.draw_rectangle(r, Color::blue());
+
+ if (button_blue_team.has_focus()) {
+ painter.draw_rectangle({r.location().x() - 1, r.location().y() - 1, r.size().width() + 2, r.size().height() + 2}, Color::yellow());
+ }
+
+ auto style_white = Style{
+ .font = ui::font::fixed_8x16,
+ .background = Color::dark_blue(),
+ .foreground = Color::white()};
+ painter.draw_string(r.center() - Point(24, 16), style_white, "BLUE");
+ painter.draw_string(r.center() - Point(24, 0), style_white, "TEAM");
+ }
+
+ return;
+ }
+
+ Color team_color = is_red_team ? Color::red() : Color::blue();
+ painter.fill_rectangle({0, 5, 240, 16}, team_color);
+
+ auto style_white = Style{
+ .font = ui::font::fixed_8x16,
+ .background = team_color,
+ .foreground = Color::white()};
+ painter.draw_string({85, 5}, style_white, is_red_team ? "RED TEAM" : "BLUE TEAM");
+
+ auto style_status = Style{
+ .font = ui::font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::white()};
+ painter.fill_rectangle({0, 21, 240, 16}, Color::black());
+ painter.draw_string({10, 21}, style_status, current_status);
+
+ if (game_state != GameState::MENU) {
+ painter.draw_string({170, 21}, style_status, current_score);
+ }
+
+ if (game_state == GameState::PLACING_SHIPS) {
+ draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
+ if (current_ship_index < 5) {
+ draw_ship_preview(painter);
+ }
+ } else if (game_state == GameState::MY_TURN) {
+ draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, enemy_grid, false, true);
+ painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
+ "Enemy ships: " + to_string_dec_uint(enemy_ships_remaining));
+ } else if (game_state == GameState::OPPONENT_TURN || game_state == GameState::WAITING_FOR_OPPONENT) {
+ draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
+ painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
+ "Your ships: " + to_string_dec_uint(ships_remaining));
+ } else if (game_state == GameState::GAME_OVER) {
+ painter.draw_string({50, 150}, style_status, "Game Over!");
+ painter.draw_string({30, 170}, style_status, current_status);
+ }
+}
+
+void BattleshipView::draw_menu_screen(Painter& painter) {
+ painter.draw_hline({12, 38}, 216, Color::dark_cyan());
+
+ painter.fill_rectangle({13, 41, 214, 116}, Color::dark_grey());
+ painter.draw_hline({12, 40}, 216, Color::cyan());
+ painter.draw_hline({12, 157}, 216, Color::cyan());
+
+ painter.fill_rectangle({13, 165, 214, 43}, Color::dark_grey());
+ painter.draw_hline({12, 164}, 216, Color::cyan());
+ painter.draw_hline({12, 208}, 216, Color::cyan());
+
+ painter.fill_rectangle({13, 218, 214, 73}, Color::dark_grey());
+ painter.draw_hline({12, 217}, 216, Color::cyan());
+ painter.draw_hline({12, 291}, 216, Color::cyan());
+
+ // Radio status indicator
+ Point indicator_pos{220, 53};
+ if (is_transmitting) {
+ painter.fill_rectangle({indicator_pos, {6, 6}}, Color::red());
+ painter.draw_rectangle({indicator_pos.x() - 1, indicator_pos.y() - 1, 8, 8}, Color::light_grey());
+ } else {
+ painter.fill_rectangle({indicator_pos, {6, 6}}, Color::green());
+ painter.draw_rectangle({indicator_pos.x() - 1, indicator_pos.y() - 1, 8, 8}, Color::light_grey());
+ }
+}
+
+void BattleshipView::draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid) {
+ painter.fill_rectangle({grid_x, grid_y, GRID_SIZE * CELL_SIZE, GRID_SIZE * CELL_SIZE},
+ Color::dark_blue());
+
+ for (uint8_t i = 0; i <= GRID_SIZE; i++) {
+ painter.draw_vline({grid_x + i * CELL_SIZE, grid_y},
+ GRID_SIZE * CELL_SIZE, Color::grey());
+ painter.draw_hline({grid_x, grid_y + i * CELL_SIZE},
+ GRID_SIZE * CELL_SIZE, Color::grey());
+ }
+
+ for (uint8_t y = 0; y < GRID_SIZE; y++) {
+ for (uint8_t x = 0; x < GRID_SIZE; x++) {
+ draw_cell(painter, grid_x + x * CELL_SIZE + 1, grid_y + y * CELL_SIZE + 1,
+ grid[y][x], show_ships, is_offense_grid,
+ is_cursor_at(x, y, is_offense_grid));
+ }
+ }
+}
+
+void BattleshipView::draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor) {
+ Color cell_color = Color::dark_blue();
+ bool should_fill = false;
+
+ if (game_state == GameState::PLACING_SHIPS && !is_offense_grid && current_ship_index < 5) {
+ uint8_t ship_size = my_ships[current_ship_index].size();
+ for (uint8_t i = 0; i < ship_size; i++) {
+ uint8_t preview_x = placing_horizontal ? cursor_x + i : cursor_x;
+ uint8_t preview_y = placing_horizontal ? cursor_y : cursor_y + i;
+ uint8_t grid_x = (cell_x - 1) / CELL_SIZE;
+ uint8_t grid_y = (cell_y - GRID_OFFSET_Y - 6) / CELL_SIZE;
+ if (grid_x == preview_x && grid_y == preview_y && preview_x < GRID_SIZE && preview_y < GRID_SIZE) {
+ return;
+ }
+ }
+ }
+
+ switch (state) {
+ case CellState::SHIP:
+ if (show_ships) {
+ cell_color = Color::grey();
+ should_fill = true;
+ }
+ break;
+ case CellState::HIT:
+ cell_color = Color::red();
+ should_fill = true;
+ break;
+ case CellState::MISS:
+ cell_color = Color::light_grey();
+ should_fill = true;
+ break;
+ case CellState::SUNK:
+ cell_color = Color::dark_red();
+ should_fill = true;
+ break;
+ default:
+ if (is_offense_grid && state == CellState::EMPTY) {
+ cell_color = Color::dark_grey();
+ should_fill = true;
+ }
+ break;
+ }
+
+ if (should_fill) {
+ painter.fill_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, cell_color);
+ }
+
+ if (state == CellState::HIT || state == CellState::SUNK) {
+ painter.draw_hline({cell_x + 4, cell_y + 4}, CELL_SIZE - 10, Color::white());
+ painter.draw_hline({cell_x + 4, cell_y + CELL_SIZE - 6}, CELL_SIZE - 10, Color::white());
+ painter.draw_vline({cell_x + 4, cell_y + 4}, CELL_SIZE - 10, Color::white());
+ painter.draw_vline({cell_x + CELL_SIZE - 6, cell_y + 4}, CELL_SIZE - 10, Color::white());
+ } else if (state == CellState::MISS) {
+ painter.draw_hline({cell_x + 8, cell_y + 4}, 8, Color::white());
+ painter.draw_hline({cell_x + 8, cell_y + CELL_SIZE - 6}, 8, Color::white());
+ painter.draw_vline({cell_x + 4, cell_y + 8}, 8, Color::white());
+ painter.draw_vline({cell_x + CELL_SIZE - 6, cell_y + 8}, 8, Color::white());
+ }
+
+ if (is_cursor) {
+ painter.draw_rectangle({cell_x - 1, cell_y - 1, CELL_SIZE, CELL_SIZE},
+ is_offense_grid && game_state == GameState::MY_TURN ? Color::yellow() : Color::cyan());
+ }
+}
+
+bool BattleshipView::is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid) {
+ if (game_state == GameState::PLACING_SHIPS && !is_offense_grid) {
+ return x == cursor_x && y == cursor_y;
+ } else if (is_offense_grid && game_state == GameState::MY_TURN) {
+ return x == target_x && y == target_y;
+ }
+ return false;
+}
+
+void BattleshipView::draw_ship_preview(Painter& painter) {
+ if (current_ship_index >= 5) return;
+
+ const Ship& ship = my_ships[current_ship_index];
+ uint8_t size = ship.size();
+ bool can_place = can_place_ship(cursor_x, cursor_y, size, placing_horizontal);
+
+ for (uint8_t i = 0; i < size; i++) {
+ uint8_t x = placing_horizontal ? cursor_x + i : cursor_x;
+ uint8_t y = placing_horizontal ? cursor_y : cursor_y + i;
+
+ if (x < GRID_SIZE && y < GRID_SIZE) {
+ uint8_t cell_x = GRID_OFFSET_X + x * CELL_SIZE + 1;
+ uint8_t cell_y = GRID_OFFSET_Y + 5 + y * CELL_SIZE + 1;
+
+ Color preview_color = can_place ? Color::green() : Color::red();
+
+ painter.fill_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, preview_color);
+ painter.draw_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, Color::white());
+ }
+ }
+}
+
+bool BattleshipView::can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal) {
+ if ((horizontal && x + size > GRID_SIZE) || (!horizontal && y + size > GRID_SIZE)) {
+ return false;
+ }
+
+ for (uint8_t i = 0; i < size; i++) {
+ uint8_t check_x = horizontal ? x + i : x;
+ uint8_t check_y = horizontal ? y : y + i;
+
+ if (my_grid[check_y][check_x] != CellState::EMPTY) {
+ return false;
+ }
+
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++) {
+ int adj_x = check_x + dx;
+ int adj_y = check_y + dy;
+
+ if (adj_x >= 0 && adj_x < GRID_SIZE &&
+ adj_y >= 0 && adj_y < GRID_SIZE) {
+ if (my_grid[adj_y][adj_x] == CellState::SHIP) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void BattleshipView::place_ship() {
+ if (current_ship_index >= 5) return;
+
+ Ship& ship = my_ships[current_ship_index];
+ uint8_t size = ship.size();
+
+ if (!can_place_ship(cursor_x, cursor_y, size, placing_horizontal)) {
+ current_status = "Invalid placement!";
+ set_dirty();
+ return;
+ }
+
+ ship.x = cursor_x;
+ ship.y = cursor_y;
+ ship.horizontal = placing_horizontal;
+ ship.placed = true;
+
+ for (uint8_t i = 0; i < size; i++) {
+ uint8_t x = placing_horizontal ? cursor_x + i : cursor_x;
+ uint8_t y = placing_horizontal ? cursor_y : cursor_y + i;
+ my_grid[y][x] = CellState::SHIP;
+ }
+
+ current_ship_index++;
+
+ if (current_ship_index >= 5) {
+ button_rotate.hidden(true);
+ button_place.hidden(true);
+
+ send_message({MessageType::READY, 0, 0});
+
+ if (is_red_team) {
+ game_state = GameState::MY_TURN;
+ current_status = "Your turn! Fire!";
+ button_fire.hidden(false);
+ button_fire.set_focusable(false);
+ touch_enabled = true;
+ } else {
+ game_state = GameState::WAITING_FOR_OPPONENT;
+ current_status = "Waiting for Red...";
+ touch_enabled = false;
+ }
+
+ focus();
+ } else {
+ static const char* ship_names[] = {"carrier (5)", "battleship (4)", "cruiser (3)",
+ "submarine (3)", "destroyer (2)"};
+ current_status = "Place ";
+ current_status += ship_names[current_ship_index];
+ }
+
+ set_dirty();
+}
+
+void BattleshipView::send_message(const GameMessage& msg) {
+ static const char* msg_strings[] = {"READY", "SHOT:", "HIT:", "MISS:", "SUNK:", "WIN"};
+
+ std::string message = msg_strings[static_cast(msg.type)];
+ if (msg.type != MessageType::READY && msg.type != MessageType::WIN) {
+ message += to_string_dec_uint(msg.x) + "," + to_string_dec_uint(msg.y);
+ }
+
+ configure_radio_tx();
+
+ uint32_t target_address = is_red_team ? BLUE_TEAM_ADDRESS : RED_TEAM_ADDRESS;
+
+ std::vector codewords;
+ BCHCode BCH_code{{1, 0, 1, 0, 0, 1}, 5, 31, 21, 2};
+
+ pocsag::pocsag_encode(pocsag::MessageType::ALPHANUMERIC, BCH_code, 0, message, target_address, codewords);
+
+ uint8_t* data_ptr = shared_memory.bb_data.data;
+ size_t bi = 0;
+
+ for (size_t i = 0; i < codewords.size(); i++) {
+ uint32_t codeword = codewords[i];
+ data_ptr[bi++] = (codeword >> 24) & 0xFF;
+ data_ptr[bi++] = (codeword >> 16) & 0xFF;
+ data_ptr[bi++] = (codeword >> 8) & 0xFF;
+ data_ptr[bi++] = codeword & 0xFF;
+ }
+
+ baseband::set_fsk_data(
+ codewords.size() * 32,
+ 2280000 / 1200,
+ 4500,
+ 64);
+
+ transmitter_model.set_baseband_bandwidth(1750000);
+ transmitter_model.enable();
+
+ current_status = "TX: " + message;
+ set_dirty();
+}
+
+void BattleshipView::on_pocsag_packet(const POCSAGPacketMessage* message) {
+ if (message->packet.flag() != pocsag::NORMAL) {
+ return;
+ }
+
+ pocsag_state.codeword_index = 0;
+ pocsag_state.errors = 0;
+
+ while (pocsag::pocsag_decode_batch(message->packet, pocsag_state)) {
+ if (pocsag_state.out_type == pocsag::MESSAGE) {
+ uint32_t expected_address = is_red_team ? RED_TEAM_ADDRESS : BLUE_TEAM_ADDRESS;
+ if (pocsag_state.address == expected_address) {
+ process_message(pocsag_state.output);
+ }
+ }
+ }
+}
+
+void BattleshipView::on_tx_progress(const uint32_t progress, const bool done) {
+ (void)progress;
+
+ if (done) {
+ transmitter_model.disable();
+ chThdSleepMilliseconds(200);
+ configure_radio_rx();
+
+ if (game_state == GameState::MY_TURN) {
+ current_status = "Waiting for response";
+ set_dirty();
+ }
+ }
+}
+
+bool BattleshipView::parse_coords(const std::string& coords, uint8_t& x, uint8_t& y) {
+ size_t comma_pos = coords.find(',');
+ if (comma_pos == std::string::npos) return false;
+
+ x = 0;
+ y = 0;
+
+ for (size_t i = 0; i < comma_pos; i++) {
+ char c = coords[i];
+ if (c >= '0' && c <= '9') {
+ x = x * 10 + (c - '0');
+ }
+ }
+
+ for (size_t i = comma_pos + 1; i < coords.length(); i++) {
+ char c = coords[i];
+ if (c >= '0' && c <= '9') {
+ y = y * 10 + (c - '0');
+ }
+ }
+
+ return x < GRID_SIZE && y < GRID_SIZE;
+}
+
+void BattleshipView::mark_ship_sunk(uint8_t x, uint8_t y, std::array, GRID_SIZE>& grid) {
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++) {
+ int check_x = x + dx;
+ int check_y = y + dy;
+ if (check_x >= 0 && check_x < GRID_SIZE &&
+ check_y >= 0 && check_y < GRID_SIZE) {
+ if (grid[check_y][check_x] == CellState::HIT) {
+ grid[check_y][check_x] = CellState::SUNK;
+ }
+ }
+ }
+ }
+}
+
+void BattleshipView::process_message(const std::string& message) {
+ if (message.empty()) return;
+
+ size_t colon_pos = message.find(':');
+ std::string msg_type = (colon_pos != std::string::npos)
+ ? message.substr(0, colon_pos)
+ : message;
+
+ if (msg_type == "READY") {
+ opponent_ready = true;
+ if (!is_red_team && game_state == GameState::WAITING_FOR_OPPONENT) {
+ current_status = "Red ready! Waiting...";
+ set_dirty();
+ }
+ } else if (msg_type == "SHOT" && colon_pos != std::string::npos) {
+ if (game_state == GameState::OPPONENT_TURN ||
+ (game_state == GameState::WAITING_FOR_OPPONENT && !is_red_team)) {
+ uint8_t x, y;
+ if (parse_coords(message.substr(colon_pos + 1), x, y)) {
+ process_shot(x, y);
+ }
+ }
+ } else if ((msg_type == "HIT" || msg_type == "MISS" || msg_type == "SUNK") && colon_pos != std::string::npos) {
+ uint8_t x, y;
+ if (parse_coords(message.substr(colon_pos + 1), x, y)) {
+ if (msg_type == "HIT") {
+ enemy_grid[y][x] = CellState::HIT;
+ current_status = "Hit! Fire again!";
+ } else if (msg_type == "MISS") {
+ enemy_grid[y][x] = CellState::MISS;
+ current_status = "Miss! Enemy turn";
+ game_state = GameState::OPPONENT_TURN;
+ button_fire.hidden(true);
+ touch_enabled = false;
+ } else if (msg_type == "SUNK") {
+ enemy_grid[y][x] = CellState::SUNK;
+ enemy_ships_remaining--;
+ current_status = "Ship sunk! Fire!";
+ mark_ship_sunk(x, y, enemy_grid);
+ }
+
+ if (game_state == GameState::MY_TURN) {
+ touch_enabled = true;
+ }
+ }
+ } else if (msg_type == "WIN") {
+ game_state = GameState::GAME_OVER;
+ current_status = (is_red_team ? "BLUE" : "RED") + std::string(" TEAM WINS!");
+ button_fire.hidden(true);
+ touch_enabled = false;
+ losses++;
+ update_score();
+ }
+
+ set_dirty();
+}
+
+void BattleshipView::fire_at_position() {
+ if (game_state != GameState::MY_TURN) return;
+
+ if (enemy_grid[target_y][target_x] != CellState::EMPTY) {
+ current_status = "Already fired!";
+ set_dirty();
+ return;
+ }
+
+ send_message({MessageType::SHOT, target_x, target_y});
+ current_status = "Firing...";
+ touch_enabled = false;
+ set_dirty();
+}
+
+void BattleshipView::process_shot(uint8_t x, uint8_t y) {
+ if (my_grid[y][x] == CellState::SHIP) {
+ my_grid[y][x] = CellState::HIT;
+
+ bool sunk = false;
+
+ for (auto& ship : my_ships) {
+ if (!ship.placed) continue;
+
+ bool hit_this_ship = false;
+ for (uint8_t i = 0; i < ship.size(); i++) {
+ uint8_t check_x = ship.horizontal ? ship.x + i : ship.x;
+ uint8_t check_y = ship.horizontal ? ship.y : ship.y + i;
+
+ if (check_x == x && check_y == y) {
+ hit_this_ship = true;
+ ship.hits++;
+ break;
+ }
+ }
+
+ if (hit_this_ship && ship.is_sunk()) {
+ sunk = true;
+ ships_remaining--;
+
+ for (uint8_t i = 0; i < ship.size(); i++) {
+ uint8_t sunk_x = ship.horizontal ? ship.x + i : ship.x;
+ uint8_t sunk_y = ship.horizontal ? ship.y : ship.y + i;
+ my_grid[sunk_y][sunk_x] = CellState::SUNK;
+ }
+ break;
+ }
+ }
+
+ if (sunk) {
+ send_message({MessageType::SUNK, x, y});
+
+ if (ships_remaining == 0) {
+ send_message({MessageType::WIN, 0, 0});
+ game_state = GameState::GAME_OVER;
+ current_status = (is_red_team ? "RED" : "BLUE") + std::string(" TEAM WINS!");
+ wins++;
+ update_score();
+ }
+ } else {
+ send_message({MessageType::HIT, x, y});
+ }
+ } else {
+ my_grid[y][x] = CellState::MISS;
+ send_message({MessageType::MISS, x, y});
+
+ game_state = GameState::MY_TURN;
+ button_fire.hidden(false);
+ touch_enabled = true;
+ current_status = "Your turn! Fire!";
+ }
+
+ set_dirty();
+}
+
+void BattleshipView::update_score() {
+ current_score = "W:" + to_string_dec_uint(wins) + " L:" + to_string_dec_uint(losses);
+}
+
+bool BattleshipView::on_touch(const TouchEvent event) {
+ if (event.type != TouchEvent::Type::Start || !touch_enabled) {
+ return false;
+ }
+
+ uint16_t x = event.point.x();
+ uint16_t y = event.point.y();
+
+ if (x >= GRID_OFFSET_X && x < GRID_OFFSET_X + GRID_SIZE * CELL_SIZE &&
+ y >= GRID_OFFSET_Y + 5 && y < GRID_OFFSET_Y + 5 + GRID_SIZE * CELL_SIZE) {
+ uint8_t grid_x = (x - GRID_OFFSET_X) / CELL_SIZE;
+ uint8_t grid_y = (y - GRID_OFFSET_Y - 5) / CELL_SIZE;
+
+ if (game_state == GameState::PLACING_SHIPS) {
+ cursor_x = grid_x;
+ cursor_y = grid_y;
+ } else if (game_state == GameState::MY_TURN) {
+ target_x = grid_x;
+ target_y = grid_y;
+ }
+ set_dirty();
+ return true;
+ }
+
+ return false;
+}
+
+bool BattleshipView::on_encoder(const EncoderEvent delta) {
+ if (delta == 0) return false;
+
+ if (game_state == GameState::PLACING_SHIPS) {
+ placing_horizontal = !placing_horizontal;
+ } else if (game_state == GameState::MY_TURN) {
+ target_x = (delta > 0) ? (target_x + 1) % GRID_SIZE : (target_x == 0) ? GRID_SIZE - 1
+ : target_x - 1;
+ }
+ set_dirty();
+ return true;
+}
+
+bool BattleshipView::on_key(const KeyEvent key) {
+ if (game_state == GameState::MENU) {
+ if (key == KeyEvent::Up || key == KeyEvent::Down ||
+ key == KeyEvent::Left || key == KeyEvent::Right) {
+ return false;
+ }
+ if (key == KeyEvent::Select || key == KeyEvent::Back) {
+ return false;
+ }
+ return false;
+ }
+
+ // Game state key handling
+ if (key == KeyEvent::Select) {
+ if (game_state == GameState::PLACING_SHIPS) {
+ place_ship();
+ return true;
+ } else if (game_state == GameState::MY_TURN) {
+ fire_at_position();
+ return true;
+ }
+ } else if (key == KeyEvent::Back) {
+ if (game_state != GameState::MENU) {
+ reset_game();
+ return true;
+ }
+ } else if (key == KeyEvent::Up || key == KeyEvent::Down) {
+ uint8_t* coord_y = (game_state == GameState::PLACING_SHIPS) ? &cursor_y : &target_y;
+ if (key == KeyEvent::Up) {
+ *coord_y = (*coord_y == 0) ? GRID_SIZE - 1 : *coord_y - 1;
+ } else {
+ *coord_y = (*coord_y + 1) % GRID_SIZE;
+ }
+ set_dirty();
+ return true;
+ } else if (key == KeyEvent::Left || key == KeyEvent::Right) {
+ uint8_t* coord_x = (game_state == GameState::PLACING_SHIPS) ? &cursor_x : &target_x;
+ if (key == KeyEvent::Left) {
+ *coord_x = (*coord_x == 0) ? GRID_SIZE - 1 : *coord_x - 1;
+ } else {
+ *coord_x = (*coord_x + 1) % GRID_SIZE;
+ }
+ set_dirty();
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace ui::external_app::battleship
diff --git a/firmware/application/external/battleship/ui_battleship.hpp b/firmware/application/external/battleship/ui_battleship.hpp
new file mode 100644
index 000000000..fc44c23db
--- /dev/null
+++ b/firmware/application/external/battleship/ui_battleship.hpp
@@ -0,0 +1,250 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_BATTLESHIP_H__
+#define __UI_BATTLESHIP_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "ui_transmitter.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "audio.hpp"
+#include "message.hpp"
+#include "pocsag.hpp"
+
+#include
+
+namespace ui::external_app::battleship {
+
+constexpr uint8_t GRID_SIZE = 10;
+constexpr uint8_t CELL_SIZE = 24;
+constexpr uint8_t GRID_OFFSET_X = 0;
+constexpr uint8_t GRID_OFFSET_Y = 32;
+
+enum class ShipType : uint8_t {
+ CARRIER = 5,
+ BATTLESHIP = 4,
+ CRUISER = 3,
+ SUBMARINE = 3,
+ DESTROYER = 2
+};
+
+enum class GameState : uint8_t {
+ MENU,
+ PLACING_SHIPS,
+ WAITING_FOR_OPPONENT,
+ MY_TURN,
+ OPPONENT_TURN,
+ GAME_OVER
+};
+
+enum class CellState : uint8_t {
+ EMPTY,
+ SHIP,
+ HIT,
+ MISS,
+ SUNK
+};
+
+enum class MessageType : uint8_t {
+ READY,
+ SHOT,
+ HIT,
+ MISS,
+ SUNK,
+ WIN
+};
+
+struct Ship {
+ ShipType type;
+ uint8_t x;
+ uint8_t y;
+ bool horizontal;
+ uint8_t hits;
+ bool placed;
+
+ uint8_t size() const {
+ return static_cast(type);
+ }
+
+ bool is_sunk() const {
+ return hits >= size();
+ }
+};
+
+struct GameMessage {
+ MessageType type;
+ uint8_t x;
+ uint8_t y;
+};
+
+class BattleshipView : public View {
+ public:
+ BattleshipView(NavigationView& nav);
+ ~BattleshipView();
+
+ void focus() override;
+ void paint(Painter& painter) override;
+ bool on_touch(const TouchEvent event) override;
+ bool on_encoder(const EncoderEvent delta) override;
+ bool on_key(const KeyEvent key) override;
+
+ std::string title() const override { return "Battleship"; }
+
+ private:
+ NavigationView& nav_;
+
+ RxRadioState rx_radio_state_{
+ 433920000 /* frequency */,
+ 1750000 /* bandwidth */,
+ 2280000 /* sampling rate */
+ };
+
+ TxRadioState tx_radio_state_{
+ 433920000 /* frequency */,
+ 1750000 /* bandwidth */,
+ 2280000 /* sampling rate */
+ };
+
+ // Settings
+ bool sound_enabled{true};
+ bool rf_amp_enabled{false};
+ uint8_t lna_gain{24};
+ uint8_t vga_gain{24};
+ uint8_t tx_gain{35};
+
+ app_settings::SettingsManager settings_{
+ "battleship",
+ app_settings::Mode::RX_TX,
+ {{"rx_freq"sv, &rx_frequency},
+ {"tx_freq"sv, &tx_frequency},
+ {"rf_amp"sv, &rf_amp_enabled}}};
+
+ GameState game_state{GameState::MENU};
+ bool is_red_team{false};
+ bool opponent_ready{false};
+ uint8_t wins{0};
+ uint8_t losses{0};
+
+ std::array, GRID_SIZE> my_grid{};
+ std::array, GRID_SIZE> enemy_grid{};
+
+ std::string current_status{"Choose your team!"};
+ std::string current_score{"W:0 L:0"};
+
+ std::array my_ships{};
+ uint8_t current_ship_index{0};
+ bool placing_horizontal{true};
+ uint8_t ships_remaining{5};
+ uint8_t enemy_ships_remaining{5};
+
+ uint8_t cursor_x{0};
+ uint8_t cursor_y{0};
+ uint8_t target_x{0};
+ uint8_t target_y{0};
+ bool touch_enabled{true};
+
+ uint32_t tx_frequency{433920000};
+ uint32_t rx_frequency{433920000};
+ bool is_transmitting{false};
+
+ // POCSAG decoding state
+ pocsag::EccContainer ecc{};
+ pocsag::POCSAGState pocsag_state{&ecc};
+ uint32_t last_address{0};
+
+ // UI Elements - Menu/Settings Screen
+ Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
+ Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
+
+ Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
+ Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
+ ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
+
+ // Radio controls
+ Text label_rf_amp{{17, 90, 35, 16}, "AMP:"};
+ Checkbox checkbox_rf_amp{{55, 90}, 3, "", false};
+
+ Text label_lna{{17, 118, 30, 16}, "LNA:"};
+ NumberField field_lna{{50, 118}, 2, {0, 40}, 8, ' '};
+
+ Text label_vga{{90, 118, 30, 16}, "VGA:"};
+ NumberField field_vga{{125, 118}, 2, {0, 62}, 8, ' '};
+
+ Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
+ NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
+
+ Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
+ Text label_audio{{17, 169, 80, 16}, "AUDIO"};
+ Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
+ Text label_volume{{110, 187, 50, 16}, "Volume:"};
+ AudioVolumeField field_volume{{165, 187}};
+
+ Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
+ Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
+ Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
+ Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
+
+ // In-game UI elements
+ RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
+ Text text_status{{10, 16, 220, 16}, ""};
+ Text text_score{{170, 16, 60, 16}, ""};
+ Button button_rotate{{10, 265, 65, 32}, "Rotate"};
+ Button button_place{{82, 265, 65, 32}, "Place"};
+ Button button_fire{{82, 265, 65, 32}, "Fire!"};
+ Button button_menu{{155, 265, 65, 32}, "Menu"};
+
+ // Methods
+ void init_game();
+ void reset_game();
+ void start_team(bool red);
+ void setup_ships();
+ void draw_menu_screen(Painter& painter);
+ void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
+ void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
+ void draw_ship_preview(Painter& painter);
+ void place_ship();
+ bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
+ void fire_at_position();
+ void process_shot(uint8_t x, uint8_t y);
+ void update_score();
+ bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
+
+ void send_message(const GameMessage& msg);
+ void process_message(const std::string& message);
+ bool parse_coords(const std::string& coords, uint8_t& x, uint8_t& y);
+ void mark_ship_sunk(uint8_t x, uint8_t y, std::array, GRID_SIZE>& grid);
+
+ void configure_radio_tx();
+ void configure_radio_rx();
+
+ MessageHandlerRegistration message_handler_packet{
+ Message::ID::POCSAGPacket,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ on_pocsag_packet(message);
+ }};
+
+ MessageHandlerRegistration message_handler_tx_progress{
+ Message::ID::TXProgress,
+ [this](const Message* const p) {
+ const auto message = static_cast(p);
+ on_tx_progress(message->progress, message->done);
+ }};
+
+ void on_pocsag_packet(const POCSAGPacketMessage* message);
+ void on_tx_progress(const uint32_t progress, const bool done);
+};
+
+} // namespace ui::external_app::battleship
+
+#endif
diff --git a/firmware/application/external/blackjack/main.cpp b/firmware/application/external/blackjack/main.cpp
new file mode 100644
index 000000000..cb27fc4ed
--- /dev/null
+++ b/firmware/application/external/blackjack/main.cpp
@@ -0,0 +1,53 @@
+/*
+ * Blackjack Game for Portapack Mayhem
+ * Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
+ * Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
+ */
+
+#include "ui.hpp"
+#include "ui_blackjack.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::blackjack {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::blackjack
+
+extern "C" {
+
+__attribute__((section(".external_app.app_blackjack.application_information"), used)) application_information_t _application_information_blackjack = {
+ (uint8_t*)0x00000000,
+ ui::external_app::blackjack::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+
+ "Blackjack",
+ {
+ // Diamond icon 16x16
+ 0x00, 0x00, // ................
+ 0x80, 0x01, // .......##.......
+ 0xC0, 0x03, // ......####......
+ 0xE0, 0x07, // .....######.....
+ 0xF0, 0x0F, // ....########....
+ 0xF8, 0x1F, // ...##########...
+ 0xFC, 0x3F, // ..############..
+ 0xFE, 0x7F, // .##############.
+ 0xFE, 0x7F, // .##############.
+ 0xFC, 0x3F, // ..############..
+ 0xF8, 0x1F, // ...##########...
+ 0xF0, 0x0F, // ....########....
+ 0xE0, 0x07, // .....######.....
+ 0xC0, 0x03, // ......####......
+ 0x80, 0x01, // .......##.......
+ 0x00, 0x00, // ................
+ },
+ ui::Color::red().v, // Red color for diamonds
+ app_location_t::GAMES,
+ -1,
+
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+}
diff --git a/firmware/application/external/blackjack/ui_blackjack.cpp b/firmware/application/external/blackjack/ui_blackjack.cpp
new file mode 100644
index 000000000..4d93b98ef
--- /dev/null
+++ b/firmware/application/external/blackjack/ui_blackjack.cpp
@@ -0,0 +1,727 @@
+/*
+ * Blackjack Game for Portapack Mayhem
+ * Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
+ * Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
+ */
+
+#include "ui_blackjack.hpp"
+
+namespace ui::external_app::blackjack {
+
+// Global variables
+static BlackjackView* current_instance = nullptr;
+static Callback game_update_callback = nullptr;
+static uint32_t game_update_timeout = 0;
+static uint32_t game_update_counter = 0;
+static Painter painter;
+
+void check_game_timer() {
+ if (game_update_callback) {
+ if (++game_update_counter >= game_update_timeout) {
+ game_update_counter = 0;
+ game_update_callback();
+ }
+ }
+}
+
+void Ticker::attach(Callback func, double delay_sec) {
+ game_update_callback = func;
+ game_update_timeout = delay_sec * 60;
+}
+
+void Ticker::detach() {
+ game_update_callback = nullptr;
+}
+
+void game_timer_check() {
+ if (current_instance) {
+ current_instance->update_game();
+ }
+}
+
+BlackjackView::BlackjackView(NavigationView& nav)
+ : nav_{nav}, game_timer{} {
+ add_children({&dummy});
+ current_instance = this;
+ game_timer.attach(&game_timer_check, 1.0 / 60.0);
+}
+
+BlackjackView::~BlackjackView() {
+ current_instance = nullptr;
+}
+
+void BlackjackView::on_show() {
+ draw_menu_static();
+}
+
+void BlackjackView::paint(Painter& painter) {
+ (void)painter;
+
+ if (!initialized) {
+ initialized = true;
+ std::srand(LPC_RTC->CTIME0);
+ init_deck();
+ }
+}
+
+void BlackjackView::frame_sync() {
+ check_game_timer();
+ set_dirty();
+}
+
+void BlackjackView::clear_screen() {
+ painter.fill_rectangle({0, 0, 240, 320}, Color::black());
+}
+
+void BlackjackView::init_deck() {
+ // Initialize deck with card values 0-51
+ // 0-12 = Spades (A-K), 13-25 = Hearts, 26-38 = Diamonds, 39-51 = Clubs
+ for (int i = 0; i < 52; i++) {
+ deck[i] = i;
+ }
+ shuffle_deck();
+}
+
+void BlackjackView::shuffle_deck() {
+ // Simple shuffle using random swaps
+ for (int i = 51; i > 0; i--) {
+ int j = rand() % (i + 1);
+ uint8_t temp = deck[i];
+ deck[i] = deck[j];
+ deck[j] = temp;
+ }
+ deck_position = 0;
+}
+
+uint8_t BlackjackView::draw_card() {
+ if (deck_position >= 52) {
+ shuffle_deck();
+ }
+ return deck[deck_position++];
+}
+
+int BlackjackView::get_card_value(uint8_t card) {
+ int value = (card % 13) + 1; // 1-13
+ if (value > 10) value = 10; // Face cards worth 10
+ return value;
+}
+
+uint8_t BlackjackView::get_card_suit(uint8_t card) {
+ return card / 13; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
+}
+
+std::string BlackjackView::get_card_string(uint8_t card) {
+ int value = (card % 13) + 1;
+ std::string result;
+
+ if (value == 1)
+ result = "A";
+ else if (value == 11)
+ result = "J";
+ else if (value == 12)
+ result = "Q";
+ else if (value == 13)
+ result = "K";
+ else if (value == 10)
+ result = "10"; // Special case for 10
+ else
+ result = std::to_string(value);
+
+ return result;
+}
+
+int BlackjackView::calculate_hand_value(uint8_t* cards, uint8_t count) {
+ int value = 0;
+ int aces = 0;
+
+ for (uint8_t i = 0; i < count; i++) {
+ int card_val = get_card_value(cards[i]);
+ if (card_val == 1) {
+ aces++;
+ value += 1;
+ } else {
+ value += card_val;
+ }
+ }
+
+ // Convert aces from 1 to 11 if beneficial
+ while (aces > 0 && value + 10 <= 21) {
+ value += 10;
+ aces--;
+ }
+
+ return value;
+}
+
+void BlackjackView::deal_cards() {
+ // Clear hands
+ player_card_count = 0;
+ dealer_card_count = 0;
+
+ // Deal initial cards
+ player_cards[player_card_count++] = draw_card();
+ dealer_cards[dealer_card_count++] = draw_card();
+ player_cards[player_card_count++] = draw_card();
+ dealer_cards[dealer_card_count++] = draw_card();
+
+ dealer_hidden = true;
+ game_state = GameState::PLAYING;
+}
+
+void BlackjackView::player_hit() {
+ if (player_card_count < MAX_CARDS_IN_HAND) {
+ player_cards[player_card_count++] = draw_card();
+
+ int value = calculate_hand_value(player_cards, player_card_count);
+ if (value > 21) {
+ // Bust!
+ cash = (cash >= bet) ? cash - bet : 0;
+ losses++;
+ game_state = GameState::GAME_OVER;
+ }
+ }
+}
+
+void BlackjackView::player_stay() {
+ dealer_hidden = false;
+ game_state = GameState::DEALER_TURN;
+ dealer_timer = 0;
+}
+
+void BlackjackView::dealer_turn() {
+ int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
+
+ if (dealer_value < 17 && dealer_card_count < MAX_CARDS_IN_HAND) {
+ dealer_cards[dealer_card_count++] = draw_card();
+ } else {
+ check_game_over();
+ }
+}
+
+void BlackjackView::check_game_over() {
+ int player_value = calculate_hand_value(player_cards, player_card_count);
+ int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
+
+ if (player_value > 21) {
+ // Player bust (already handled)
+ } else if (dealer_value > 21) {
+ // Dealer bust - player wins
+ cash += bet;
+ wins++;
+ } else if (player_value > dealer_value) {
+ // Player wins
+ cash += bet;
+ wins++;
+ } else if (player_value < dealer_value) {
+ // Dealer wins
+ cash = (cash >= bet) ? cash - bet : 0;
+ losses++;
+ }
+ // Else it's a tie - no money changes hands
+
+ if (cash > high_score) {
+ high_score = cash;
+ }
+
+ game_state = GameState::GAME_OVER;
+}
+
+void BlackjackView::update_game() {
+ switch (game_state) {
+ case GameState::MENU:
+ draw_menu();
+ break;
+
+ case GameState::BETTING:
+ draw_betting();
+ break;
+
+ case GameState::PLAYING:
+ draw_game();
+ break;
+
+ case GameState::DEALER_TURN:
+ if (++dealer_timer >= 60) { // 1 second delay
+ dealer_timer = 0;
+ dealer_turn();
+ }
+ draw_game();
+ break;
+
+ case GameState::GAME_OVER:
+ draw_game();
+ break;
+
+ case GameState::STATS:
+ draw_stats();
+ break;
+ }
+}
+
+void BlackjackView::draw_menu_static() {
+ clear_screen();
+
+ auto style_title = *ui::Theme::getInstance()->fg_green;
+ auto style_text = *ui::Theme::getInstance()->fg_light;
+ auto style_rules = *ui::Theme::getInstance()->fg_cyan;
+
+ painter.draw_string({84, 20}, style_title, "BLACKJACK");
+
+ // Draw rules
+ painter.draw_string({70, 55}, style_rules, "-- RULES --");
+ painter.draw_string({61, 75}, style_text, "Get close to 21");
+ painter.draw_string({61, 90}, style_text, "without going over");
+ painter.draw_string({61, 110}, style_text, "Dealer hits on 16");
+ painter.draw_string({61, 125}, style_text, "Dealer stays on 17");
+ painter.draw_string({61, 145}, style_text, "Blackjack pays 1:1");
+
+ // Controls
+ painter.draw_string({65, 175}, style_rules, "-- CONTROLS --");
+ painter.draw_string({61, 195}, style_text, "SELECT: Start/Hit");
+ painter.draw_string({61, 210}, style_text, "LEFT: Stats");
+ painter.draw_string({61, 225}, style_text, "RIGHT: Exit/Stay");
+
+ // Draw high score
+ painter.draw_string({61, 250}, style_text, "High Score: $" + std::to_string(high_score));
+}
+
+void BlackjackView::draw_menu() {
+ // Only handle the flashing text animation
+ if (++blink_counter >= 30) {
+ blink_counter = 0;
+ blink_state = !blink_state;
+
+ if (blink_state) {
+ auto style = *ui::Theme::getInstance()->fg_yellow;
+ painter.draw_string({55, 280}, style, "* PRESS SELECT *");
+ } else {
+ // Clear just the text area
+ painter.fill_rectangle({55, 278, 130, 20}, Color::black());
+ }
+ }
+}
+
+void BlackjackView::draw_stats() {
+ clear_screen();
+
+ auto style_title = *ui::Theme::getInstance()->fg_green;
+ auto style_text = *ui::Theme::getInstance()->fg_light;
+ auto style_value = *ui::Theme::getInstance()->fg_yellow;
+
+ painter.draw_string({75, 30}, style_title, "STATISTICS");
+
+ painter.draw_string({30, 80}, style_text, "Wins:");
+ painter.draw_string({150, 80}, style_value, std::to_string(wins));
+
+ painter.draw_string({30, 100}, style_text, "Losses:");
+ painter.draw_string({150, 100}, style_value, std::to_string(losses));
+
+ // Win percentage
+ uint32_t total = wins + losses;
+ if (total > 0) {
+ uint32_t win_pct = (wins * 100) / total;
+ painter.draw_string({30, 120}, style_text, "Win %:");
+ painter.draw_string({150, 120}, style_value, std::to_string(win_pct) + "%");
+ }
+
+ painter.draw_string({30, 160}, style_text, "High Score:");
+ painter.draw_string({150, 160}, style_value, "$" + std::to_string(high_score));
+
+ painter.draw_string({30, 180}, style_text, "Cash:");
+ painter.draw_string({150, 180}, style_value, "$" + std::to_string(cash));
+
+ painter.draw_string({40, 250}, style_text, "SELECT: Back");
+}
+
+void BlackjackView::draw_betting() {
+ static uint32_t last_bet = 0;
+ static bool betting_drawn = false;
+
+ if (!betting_drawn) {
+ clear_screen();
+
+ auto style_title = *ui::Theme::getInstance()->fg_green;
+ auto style_text = *ui::Theme::getInstance()->fg_light;
+
+ painter.draw_string({70, 40}, style_title, "PLACE BET");
+ painter.draw_string({30, 80}, style_text, "Cash: $" + std::to_string(cash));
+
+ painter.draw_string({30, 140}, style_text, "ENCODER: +/- $10");
+ painter.draw_string({30, 160}, style_text, "SELECT: Deal");
+
+ betting_drawn = true;
+ last_bet = 0;
+ }
+
+ // Update bet display
+ if (bet != last_bet) {
+ painter.fill_rectangle({30, 110, 150, 20}, Color::black());
+ painter.draw_string({30, 110}, *ui::Theme::getInstance()->fg_yellow, "Bet: $" + std::to_string(bet));
+ last_bet = bet;
+ }
+}
+
+void BlackjackView::draw_game() {
+ static int last_player_count = -1;
+ static int last_dealer_count = -1;
+ static GameState last_game_state = GameState::MENU;
+ static uint32_t last_bet = 0;
+
+ // Clear and redraw if hands changed or game state changed
+ if (player_card_count != last_player_count || dealer_card_count != last_dealer_count || game_state != last_game_state) {
+ clear_screen();
+
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({10, 10}, style, "Cash: $" + std::to_string(cash));
+ painter.draw_string({140, 10}, style, "Bet: $" + std::to_string(bet));
+
+ // Draw dealer hand with value next to label
+ auto style_value = *ui::Theme::getInstance()->fg_yellow;
+ painter.draw_string({10, 45}, *ui::Theme::getInstance()->fg_light, "Dealer:");
+ if (!dealer_hidden || game_state == GameState::GAME_OVER) {
+ int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
+ painter.draw_string({70, 45}, style_value, "(" + std::to_string(dealer_value) + ")");
+ }
+ draw_hand(10, 65, dealer_cards, dealer_card_count, true);
+
+ // Draw player hand with value next to label
+ painter.draw_string({10, 165}, *ui::Theme::getInstance()->fg_light, "You:");
+ int player_value = calculate_hand_value(player_cards, player_card_count);
+ painter.draw_string({50, 165}, style_value, "(" + std::to_string(player_value) + ")");
+ draw_hand(10, 185, player_cards, player_card_count, false);
+
+ // Draw controls or result
+ if (game_state == GameState::PLAYING) {
+ auto style_text = *ui::Theme::getInstance()->fg_light;
+ painter.draw_string({30, 290}, style_text, "SELECT: Hit");
+ painter.draw_string({130, 290}, style_text, "RIGHT: Stay");
+ } else if (game_state == GameState::GAME_OVER) {
+ const Style* style_result = ui::Theme::getInstance()->fg_yellow;
+ std::string result;
+
+ if (player_value > 21) {
+ result = "BUST! You Lose";
+ style_result = ui::Theme::getInstance()->fg_red;
+ } else if (calculate_hand_value(dealer_cards, dealer_card_count) > 21) {
+ result = "Dealer Bust! Win!";
+ style_result = ui::Theme::getInstance()->fg_green;
+ } else if (player_value > calculate_hand_value(dealer_cards, dealer_card_count)) {
+ result = "You Win!";
+ style_result = ui::Theme::getInstance()->fg_green;
+ } else if (player_value < calculate_hand_value(dealer_cards, dealer_card_count)) {
+ result = "You Lose";
+ style_result = ui::Theme::getInstance()->fg_red;
+ } else {
+ result = "Push (Tie)";
+ }
+
+ // Draw result
+ painter.draw_string({60, 270}, *style_result, result);
+
+ // Draw compact bet selector in top right area
+ auto style_bet = *ui::Theme::getInstance()->fg_cyan;
+ painter.draw_string({140, 25}, style_bet, "Next: $" + std::to_string(bet));
+
+ // Show controls
+ painter.draw_string({10, 290}, *ui::Theme::getInstance()->fg_light, "SELECT: Deal ENC: +/-");
+ }
+
+ last_player_count = player_card_count;
+ last_dealer_count = dealer_card_count;
+ last_game_state = game_state;
+ last_bet = bet;
+ } else if (game_state == GameState::GAME_OVER && bet != last_bet) {
+ // Only update the bet display when it changes
+ painter.fill_rectangle({140, 25, 90, 16}, Color::black());
+ painter.draw_string({140, 25}, *ui::Theme::getInstance()->fg_cyan, "Next: $" + std::to_string(bet));
+ last_bet = bet;
+ }
+}
+
+void BlackjackView::draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large) {
+ if (large) {
+ // Large suit symbols for center of card
+ switch (suit) {
+ case 0: // Spades
+ // Top curve
+ painter.fill_rectangle({x + 9, y + 4, 2, 2}, color);
+ painter.fill_rectangle({x + 8, y + 5, 4, 2}, color);
+ painter.fill_rectangle({x + 7, y + 6, 6, 2}, color);
+ painter.fill_rectangle({x + 6, y + 7, 8, 2}, color);
+ painter.fill_rectangle({x + 5, y + 8, 10, 2}, color);
+ painter.fill_rectangle({x + 4, y + 9, 12, 2}, color);
+ painter.fill_rectangle({x + 3, y + 10, 14, 2}, color);
+ painter.fill_rectangle({x + 2, y + 11, 16, 2}, color);
+ painter.fill_rectangle({x + 1, y + 12, 18, 2}, color);
+ painter.fill_rectangle({x + 0, y + 13, 20, 3}, color);
+ painter.fill_rectangle({x + 1, y + 16, 18, 2}, color);
+ painter.fill_rectangle({x + 2, y + 17, 16, 1}, color);
+ painter.fill_rectangle({x + 3, y + 18, 14, 1}, color);
+ // Stem
+ painter.fill_rectangle({x + 9, y + 19, 2, 4}, color);
+ painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
+ painter.fill_rectangle({x + 7, y + 23, 6, 1}, color);
+ painter.fill_rectangle({x + 6, y + 24, 8, 1}, color);
+ break;
+
+ case 1: // Hearts
+ // Left bump
+ painter.fill_rectangle({x + 3, y + 5, 4, 2}, color);
+ painter.fill_rectangle({x + 2, y + 6, 6, 2}, color);
+ painter.fill_rectangle({x + 1, y + 7, 8, 3}, color);
+ painter.fill_rectangle({x + 0, y + 9, 9, 3}, color);
+ // Right bump
+ painter.fill_rectangle({x + 13, y + 5, 4, 2}, color);
+ painter.fill_rectangle({x + 12, y + 6, 6, 2}, color);
+ painter.fill_rectangle({x + 11, y + 7, 8, 3}, color);
+ painter.fill_rectangle({x + 11, y + 9, 9, 3}, color);
+ // Body
+ painter.fill_rectangle({x + 1, y + 11, 18, 3}, color);
+ painter.fill_rectangle({x + 2, y + 14, 16, 2}, color);
+ painter.fill_rectangle({x + 3, y + 16, 14, 2}, color);
+ painter.fill_rectangle({x + 4, y + 18, 12, 1}, color);
+ painter.fill_rectangle({x + 5, y + 19, 10, 1}, color);
+ painter.fill_rectangle({x + 6, y + 20, 8, 1}, color);
+ painter.fill_rectangle({x + 7, y + 21, 6, 1}, color);
+ painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
+ painter.fill_rectangle({x + 9, y + 23, 2, 1}, color);
+ break;
+
+ case 2: // Diamonds
+ painter.fill_rectangle({x + 10, y + 3, 1, 1}, color);
+ painter.fill_rectangle({x + 9, y + 4, 3, 1}, color);
+ painter.fill_rectangle({x + 8, y + 5, 5, 1}, color);
+ painter.fill_rectangle({x + 7, y + 6, 7, 1}, color);
+ painter.fill_rectangle({x + 6, y + 7, 9, 1}, color);
+ painter.fill_rectangle({x + 5, y + 8, 11, 1}, color);
+ painter.fill_rectangle({x + 4, y + 9, 13, 1}, color);
+ painter.fill_rectangle({x + 3, y + 10, 15, 1}, color);
+ painter.fill_rectangle({x + 2, y + 11, 17, 1}, color);
+ painter.fill_rectangle({x + 1, y + 12, 19, 1}, color);
+ painter.fill_rectangle({x + 0, y + 13, 21, 1}, color);
+ painter.fill_rectangle({x + 1, y + 14, 19, 1}, color);
+ painter.fill_rectangle({x + 2, y + 15, 17, 1}, color);
+ painter.fill_rectangle({x + 3, y + 16, 15, 1}, color);
+ painter.fill_rectangle({x + 4, y + 17, 13, 1}, color);
+ painter.fill_rectangle({x + 5, y + 18, 11, 1}, color);
+ painter.fill_rectangle({x + 6, y + 19, 9, 1}, color);
+ painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
+ painter.fill_rectangle({x + 8, y + 21, 5, 1}, color);
+ painter.fill_rectangle({x + 9, y + 22, 3, 1}, color);
+ painter.fill_rectangle({x + 10, y + 23, 1, 1}, color);
+ break;
+
+ case 3: // Clubs
+ // Center circle
+ painter.fill_rectangle({x + 8, y + 8, 5, 1}, color);
+ painter.fill_rectangle({x + 7, y + 9, 7, 3}, color);
+ painter.fill_rectangle({x + 8, y + 12, 5, 1}, color);
+ // Left circle
+ painter.fill_rectangle({x + 3, y + 11, 4, 1}, color);
+ painter.fill_rectangle({x + 2, y + 12, 6, 3}, color);
+ painter.fill_rectangle({x + 3, y + 15, 4, 1}, color);
+ // Right circle
+ painter.fill_rectangle({x + 14, y + 11, 4, 1}, color);
+ painter.fill_rectangle({x + 13, y + 12, 6, 3}, color);
+ painter.fill_rectangle({x + 14, y + 15, 4, 1}, color);
+ // Connect circles
+ painter.fill_rectangle({x + 6, y + 13, 9, 2}, color);
+ // Stem
+ painter.fill_rectangle({x + 9, y + 16, 3, 4}, color);
+ painter.fill_rectangle({x + 8, y + 19, 5, 1}, color);
+ painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
+ painter.fill_rectangle({x + 6, y + 21, 9, 1}, color);
+ break;
+ }
+ } else {
+ // Small suit symbols
+ switch (suit) {
+ case 0: // Spades - small
+ painter.fill_rectangle({x + 2, y + 1, 3, 3}, color);
+ painter.fill_rectangle({x + 1, y + 3, 5, 2}, color);
+ painter.fill_rectangle({x + 3, y + 5, 1, 2}, color);
+ break;
+
+ case 1: // Hearts - small
+ painter.fill_rectangle({x + 1, y + 1, 2, 2}, color);
+ painter.fill_rectangle({x + 4, y + 1, 2, 2}, color);
+ painter.fill_rectangle({x + 1, y + 2, 5, 2}, color);
+ painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
+ painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
+ break;
+
+ case 2: // Diamonds - small
+ painter.fill_rectangle({x + 3, y + 1, 1, 1}, color);
+ painter.fill_rectangle({x + 2, y + 2, 3, 1}, color);
+ painter.fill_rectangle({x + 1, y + 3, 5, 1}, color);
+ painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
+ painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
+ break;
+
+ case 3: // Clubs - small
+ painter.fill_rectangle({x + 3, y + 1, 2, 2}, color);
+ painter.fill_rectangle({x + 1, y + 3, 2, 2}, color);
+ painter.fill_rectangle({x + 4, y + 3, 2, 2}, color);
+ painter.fill_rectangle({x + 3, y + 5, 2, 2}, color);
+ break;
+ }
+ }
+}
+
+void BlackjackView::draw_card(int x, int y, uint8_t card, bool hidden) {
+ const int width = 60;
+ const int height = 80;
+
+ // Draw card background
+ painter.fill_rectangle({x, y, width, height}, Color::white());
+ painter.draw_rectangle({x, y, width, height}, Color::black());
+ painter.draw_rectangle({x + 1, y + 1, width - 2, height - 2}, Color::grey()); // Inner border
+
+ if (hidden) {
+ // Draw card back pattern - diagonal lines
+ for (int i = 4; i < width - 4; i += 6) {
+ for (int j = 4; j < height - 4; j += 6) {
+ painter.fill_rectangle({x + i, y + j, 3, 3}, Color::blue());
+ painter.fill_rectangle({x + i + 3, y + j + 3, 3, 3}, Color::red());
+ }
+ }
+ } else {
+ // Draw card value
+ uint8_t suit = get_card_suit(card);
+ Color suit_color = (suit == 1 || suit == 2) ? Color::red() : Color::black();
+
+ const auto* base_style = ui::Theme::getInstance()->fg_light;
+ Style card_style{
+ .font = base_style->font,
+ .background = Color::white(),
+ .foreground = suit_color};
+
+ std::string value_str = get_card_string(card);
+
+ // Draw value in top-left corner
+ painter.draw_string({x + 4, y + 4}, card_style, value_str);
+
+ // Draw small suit symbol next to value
+ int suit_x = (value_str == "10") ? x + 20 : x + 12;
+ draw_suit_symbol(suit_x, y + 4, suit, suit_color, false);
+
+ // Draw value in bottom-right corner
+ int bottom_x = (value_str == "10") ? x + width - 24 : x + width - 16;
+ painter.draw_string({bottom_x, y + height - 18}, card_style, value_str);
+
+ // Draw small suit symbol in bottom-right
+ draw_suit_symbol(x + width - 10, y + height - 16, suit, suit_color, false);
+
+ // Draw large suit symbol in center
+ draw_suit_symbol(x + 20, y + 28, suit, suit_color, true);
+ }
+}
+
+void BlackjackView::draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer) {
+ // Calculate total width needed
+ const int card_width = 60;
+ const int overlap = 40; // Amount of overlap when cards need to fit
+ const int max_width = 230 - x; // Available width on screen
+
+ int spacing;
+ if (count == 1) {
+ spacing = 0;
+ } else if (count == 2) {
+ spacing = card_width + 5; // Small gap for 2 cards
+ } else {
+ // Calculate spacing to fit all cards
+ int total_overlap_width = card_width + (count - 1) * overlap;
+ if (total_overlap_width <= max_width) {
+ spacing = overlap;
+ } else {
+ // Need more overlap to fit
+ spacing = (max_width - card_width) / (count - 1);
+ }
+ }
+
+ for (uint8_t i = 0; i < count; i++) {
+ bool hide = is_dealer && dealer_hidden && i == 1;
+ int card_x = x + (i * spacing);
+ draw_card(card_x, y, cards[i], hide);
+ }
+}
+
+bool BlackjackView::on_key(const KeyEvent key) {
+ if (key == KeyEvent::Select) {
+ switch (game_state) {
+ case GameState::MENU:
+ if (cash < 10) {
+ cash = 100; // Reset if broke - maybe should provide https://gamblersanonymous.org/ link if they also lost their wife and house
+ wins = 0;
+ losses = 0;
+ }
+ game_state = GameState::BETTING;
+ break;
+
+ case GameState::BETTING:
+ deal_cards();
+ break;
+
+ case GameState::PLAYING:
+ player_hit();
+ break;
+
+ case GameState::GAME_OVER:
+ // Deal new hand with current bet
+ if (cash >= bet) {
+ deal_cards();
+ } else if (cash >= 10) {
+ // Not enough for current bet, reduce to what they can afford
+ bet = 10;
+ deal_cards();
+ } else {
+ // Broke, reset game
+ cash = 100;
+ wins = 0;
+ losses = 0;
+ game_state = GameState::MENU;
+ draw_menu_static();
+ }
+ break;
+
+ case GameState::STATS:
+ game_state = GameState::MENU;
+ draw_menu_static();
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ } else if (key == KeyEvent::Left) {
+ if (game_state == GameState::MENU) {
+ game_state = GameState::STATS;
+ }
+ return true;
+ } else if (key == KeyEvent::Right) {
+ if (game_state == GameState::MENU) {
+ nav_.pop();
+ return true;
+ } else if (game_state == GameState::PLAYING) {
+ player_stay();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool BlackjackView::on_encoder(const EncoderEvent delta) {
+ if (game_state == GameState::BETTING || game_state == GameState::GAME_OVER) {
+ if (delta > 0 && bet + 10 <= cash) {
+ bet += 10;
+ } else if (delta < 0 && bet >= 20) {
+ bet -= 10;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace ui::external_app::blackjack
diff --git a/firmware/application/external/blackjack/ui_blackjack.hpp b/firmware/application/external/blackjack/ui_blackjack.hpp
new file mode 100644
index 000000000..90d8d8cb6
--- /dev/null
+++ b/firmware/application/external/blackjack/ui_blackjack.hpp
@@ -0,0 +1,156 @@
+/*
+ * Blackjack Game for Portapack Mayhem
+ * Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
+ * Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
+ */
+
+#ifndef __UI_BLACKJACK_H__
+#define __UI_BLACKJACK_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "event_m0.hpp"
+#include "message.hpp"
+#include "irq_controls.hpp"
+#include "random.hpp"
+#include "lpc43xx_cpp.hpp"
+#include "ui_widget.hpp"
+#include "app_settings.hpp"
+#include
+#include
+
+namespace ui::external_app::blackjack {
+
+// Game states
+enum class GameState {
+ MENU,
+ BETTING,
+ PLAYING,
+ DEALER_TURN,
+ GAME_OVER,
+ STATS
+};
+
+// Card structure
+struct Card {
+ uint8_t value; // 1-13 (Ace=1, Jack=11, Queen=12, King=13)
+ uint8_t suit; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
+
+ Card()
+ : value(0), suit(0) {}
+ Card(uint8_t v, uint8_t s)
+ : value(v), suit(s) {}
+};
+
+// Timer class
+using Callback = void (*)(void);
+
+class Ticker {
+ public:
+ Ticker() = default;
+ void attach(Callback func, double delay_sec);
+ void detach();
+};
+
+// Function declarations
+void check_game_timer();
+void game_timer_check();
+
+class BlackjackView : public View {
+ public:
+ BlackjackView(NavigationView& nav);
+ ~BlackjackView();
+
+ void on_show() override;
+ std::string title() const override { return "Blackjack"; }
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_encoder(const EncoderEvent event) override;
+ bool on_key(KeyEvent key) override;
+
+ // Public for timer callback
+ GameState game_state = GameState::MENU;
+ void update_game();
+
+ private:
+ NavigationView& nav_;
+ bool initialized = false;
+
+ // Game variables
+ static constexpr uint8_t MAX_CARDS_IN_HAND = 11; // Maximum possible cards before bust
+ uint8_t deck[52];
+ uint8_t deck_position = 0;
+
+ uint8_t player_cards[MAX_CARDS_IN_HAND];
+ uint8_t player_card_count = 0;
+
+ uint8_t dealer_cards[MAX_CARDS_IN_HAND];
+ uint8_t dealer_card_count = 0;
+
+ // Game state variables
+ uint32_t cash = 100;
+ uint32_t bet = 10;
+ uint32_t wins = 0;
+ uint32_t losses = 0;
+ uint32_t high_score = 100;
+ bool dealer_hidden = true;
+
+ // UI state
+ uint8_t menu_selection = 0;
+ bool blink_state = true;
+ uint32_t blink_counter = 0;
+ uint32_t dealer_timer = 0;
+
+ // Timer
+ Ticker game_timer;
+
+ // Methods
+ void init_deck();
+ void shuffle_deck();
+ uint8_t draw_card();
+ void deal_cards();
+ int calculate_hand_value(uint8_t* cards, uint8_t count);
+ int get_card_value(uint8_t card);
+ uint8_t get_card_suit(uint8_t card);
+ std::string get_card_string(uint8_t card);
+ void player_hit();
+ void player_stay();
+ void dealer_turn();
+ void check_game_over();
+ void reset_game();
+
+ // Drawing methods
+ void draw_menu();
+ void draw_menu_static();
+ void draw_stats();
+ void draw_game();
+ void draw_betting();
+ void draw_card(int x, int y, uint8_t card, bool hidden = false);
+ void draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer = false);
+ void draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large);
+ void clear_screen();
+
+ // Settings
+ app_settings::SettingsManager settings_{
+ "blackjack",
+ app_settings::Mode::NO_RF,
+ {{"cash"sv, &cash},
+ {"wins"sv, &wins},
+ {"losses"sv, &losses},
+ {"highscore"sv, &high_score}}};
+
+ Button dummy{
+ {240, 0, 0, 0},
+ ""};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+
+} // namespace ui::external_app::blackjack
+
+#endif /* __UI_BLACKJACK_H__ */
diff --git a/firmware/application/external/blespam/ui_blespam.hpp b/firmware/application/external/blespam/ui_blespam.hpp
index e5831d2f7..73640328b 100644
--- a/firmware/application/external/blespam/ui_blespam.hpp
+++ b/firmware/application/external/blespam/ui_blespam.hpp
@@ -119,7 +119,7 @@ class BLESpamView : public View {
#ifdef BLESPMUSECONSOLE
Console console{
- {0, 70, 240, 220}};
+ {0, 70, screen_width, 220}};
#endif
OptionsField options_atkmode{
{0 * 8, 2 * 8},
diff --git a/firmware/application/external/breakout/main.cpp b/firmware/application/external/breakout/main.cpp
new file mode 100644
index 000000000..a4e31c860
--- /dev/null
+++ b/firmware/application/external/breakout/main.cpp
@@ -0,0 +1,70 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui.hpp"
+#include "ui_breakout.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::breakout {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::breakout
+
+extern "C" {
+
+__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
+ (uint8_t*)0x00000000,
+ ui::external_app::breakout::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+
+ "Breakout",
+ {
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x7F,
+ 0x00,
+ 0x00,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ 0xF7,
+ },
+ ui::Color::green().v,
+ app_location_t::GAMES,
+ -1,
+
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+} // namespace ui::external_app::breakout
\ No newline at end of file
diff --git a/firmware/application/external/breakout/ui_breakout.cpp b/firmware/application/external/breakout/ui_breakout.cpp
new file mode 100644
index 000000000..2287a457f
--- /dev/null
+++ b/firmware/application/external/breakout/ui_breakout.cpp
@@ -0,0 +1,552 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_breakout.hpp"
+
+namespace ui::external_app::breakout {
+
+Ticker game_timer;
+
+int paddle_x = 0;
+float ball_x = 0;
+float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
+float ball_dx = 1.5f;
+float ball_dy = -2.0f;
+int score = 0;
+int lives = 3;
+int level = 1;
+int game_state = STATE_MENU;
+bool initialized = false;
+bool ball_attached = true;
+unsigned int brick_count = 0;
+
+bool menu_initialized = false;
+bool blink_state = true;
+uint32_t blink_counter = 0;
+int16_t prompt_x = 0;
+
+bool gameover_initialized = false;
+bool gameover_blink_state = true;
+uint32_t gameover_blink_counter = 0;
+int16_t restart_x = 0;
+
+bool bricks[BRICK_ROWS][BRICK_COLS];
+int brick_colors[BRICK_ROWS];
+
+const Color pp_colors[] = {
+ Color::white(),
+ Color::blue(),
+ Color::yellow(),
+ Color::purple(),
+ Color::green(),
+ Color::red(),
+ Color::magenta(),
+ Color::orange(),
+ Color::black(),
+};
+
+Painter painter;
+
+bool but_RIGHT = false;
+bool but_LEFT = false;
+bool but_SELECT = false;
+
+static Callback game_update_callback = nullptr;
+static uint32_t game_update_timeout = 0;
+static uint32_t game_update_counter = 0;
+
+void cls() {
+ painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
+}
+
+void background(int color) {
+ (void)color;
+}
+
+void fillrect(int x1, int y1, int x2, int y2, int color) {
+ painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void rect(int x1, int y1, int x2, int y2, int color) {
+ painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void check_game_timer() {
+ if (game_update_callback) {
+ if (++game_update_counter >= game_update_timeout) {
+ game_update_counter = 0;
+ game_update_callback();
+ }
+ }
+}
+
+void Ticker::attach(Callback func, double delay_sec) {
+ game_update_callback = func;
+ game_update_timeout = delay_sec * 60;
+}
+
+void Ticker::detach() {
+ game_update_callback = nullptr;
+}
+
+void game_timer_check() {
+ if (game_state == STATE_PLAYING) {
+ update_game();
+ } else if (game_state == STATE_MENU) {
+ show_menu();
+ } else if (game_state == STATE_GAME_OVER) {
+ show_game_over();
+ }
+}
+
+void init_game() {
+ paddle_x = (screen_width - PADDLE_WIDTH) / 2;
+ score = 0;
+ lives = 3;
+ level = 1;
+
+ brick_colors[0] = Red;
+ brick_colors[1] = Orange;
+ brick_colors[2] = Yellow;
+ brick_colors[3] = Green;
+ brick_colors[4] = Purple;
+
+ init_level();
+
+ game_state = STATE_MENU;
+ menu_initialized = false;
+ blink_state = true;
+ blink_counter = 0;
+}
+
+void init_level() {
+ ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
+ ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
+
+ float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
+ ball_dx = (ball_dx > 0 ? 1.5f : -1.5f) * speed_multiplier;
+ ball_dy = -2.0f * speed_multiplier;
+
+ ball_attached = true;
+
+ brick_count = 0;
+ for (int row = 0; row < BRICK_ROWS; row++) {
+ for (int col = 0; col < BRICK_COLS; col++) {
+ bricks[row][col] = true;
+ brick_count++;
+ }
+ }
+}
+
+void draw_screen() {
+ cls();
+ background(COLOR_BACKGROUND);
+
+ draw_borders();
+ draw_bricks();
+ draw_paddle();
+ draw_ball();
+ draw_score();
+ draw_lives();
+ draw_level();
+}
+
+void draw_borders() {
+ rect(0, GAME_AREA_TOP - 1, screen_width, GAME_AREA_TOP, COLOR_BORDER);
+}
+
+void draw_bricks() {
+ for (int row = 0; row < BRICK_ROWS; row++) {
+ for (int col = 0; col < BRICK_COLS; col++) {
+ if (bricks[row][col]) {
+ int x = col * (BRICK_WIDTH + BRICK_GAP);
+ int y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
+ fillrect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, brick_colors[row]);
+ rect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, Black);
+ }
+ }
+ }
+}
+
+void draw_paddle() {
+ fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_PADDLE);
+}
+
+void draw_ball() {
+ fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BALL);
+}
+
+void draw_score() {
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
+}
+
+void draw_lives() {
+ auto style = *ui::Theme::getInstance()->fg_red;
+ painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
+}
+
+void draw_level() {
+ auto style = *ui::Theme::getInstance()->fg_yellow;
+ painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
+}
+
+void move_paddle_left() {
+ if (paddle_x > 0) {
+ fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
+ if (ball_attached) {
+ fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
+ }
+
+ paddle_x -= 10;
+ if (paddle_x < 0) paddle_x = 0;
+
+ if (ball_attached) {
+ ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
+ }
+
+ draw_paddle();
+ if (ball_attached) {
+ draw_ball();
+ }
+ }
+}
+
+void move_paddle_right() {
+ if (paddle_x < screen_width - PADDLE_WIDTH) {
+ fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
+ if (ball_attached) {
+ fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
+ }
+
+ paddle_x += 10;
+ if (paddle_x > screen_width - PADDLE_WIDTH) paddle_x = screen_width - PADDLE_WIDTH;
+
+ if (ball_attached) {
+ ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
+ }
+
+ draw_paddle();
+ if (ball_attached) {
+ draw_ball();
+ }
+ }
+}
+
+void launch_ball() {
+ if (ball_attached) {
+ ball_attached = false;
+ ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
+ ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
+ float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
+ ball_dx = 1.5f * speed_multiplier;
+ ball_dy = -2.0f * speed_multiplier;
+ }
+}
+
+void update_game() {
+ if (ball_attached) {
+ return;
+ }
+
+ fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
+
+ float next_ball_y = ball_y + ball_dy;
+ if (next_ball_y > GAME_AREA_BOTTOM) {
+ lives--;
+ draw_lives();
+ if (lives <= 0) {
+ handle_game_over();
+ } else {
+ ball_attached = true;
+ ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
+ ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
+ draw_ball();
+ }
+ return;
+ }
+
+ ball_x += ball_dx;
+ ball_y = next_ball_y;
+
+ if (ball_x < 0) {
+ ball_x = 0;
+ ball_dx = -ball_dx;
+ } else if (ball_x > screen_width - BALL_SIZE) {
+ ball_x = screen_width - BALL_SIZE;
+ ball_dx = -ball_dx;
+ }
+
+ if (ball_y < GAME_AREA_TOP) {
+ ball_y = GAME_AREA_TOP;
+ ball_dy = -ball_dy;
+ }
+
+ if (ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_HEIGHT) {
+ if (ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_WIDTH) {
+ ball_y = PADDLE_Y - BALL_SIZE;
+ float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
+ float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
+ ball_dx = angle * 4.0f;
+ if (ball_dx > -0.5f && ball_dx < 0.5f) {
+ ball_dx = (ball_dx > 0) ? 0.5f : -0.5f;
+ }
+ ball_dy = -ball_dy;
+ }
+ }
+
+ check_collisions();
+
+ draw_ball();
+
+ if (check_level_complete()) {
+ next_level();
+ }
+}
+
+void check_collisions() {
+ int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP);
+ int grid_y = (ball_y - GAME_AREA_TOP - 5) / (BRICK_HEIGHT + BRICK_GAP);
+
+ for (int row = grid_y - 1; row <= grid_y + 1; row++) {
+ for (int col = grid_x - 1; col <= grid_x + 1; col++) {
+ if (row >= 0 && row < BRICK_ROWS && col >= 0 && col < BRICK_COLS) {
+ if (bricks[row][col] && check_brick_collision(row, col)) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+bool check_brick_collision(int row, int col) {
+ int brick_x = col * (BRICK_WIDTH + BRICK_GAP);
+ int brick_y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
+
+ if (ball_x + BALL_SIZE >= brick_x && ball_x <= brick_x + BRICK_WIDTH &&
+ ball_y + BALL_SIZE >= brick_y && ball_y <= brick_y + BRICK_HEIGHT) {
+ fillrect(brick_x, brick_y, brick_x + BRICK_WIDTH, brick_y + BRICK_HEIGHT, COLOR_BACKGROUND);
+
+ bricks[row][col] = false;
+ brick_count--;
+
+ score += (5 - row) * 10;
+ draw_score();
+
+ float center_x = brick_x + BRICK_WIDTH / 2;
+ float center_y = brick_y + BRICK_HEIGHT / 2;
+ float ball_center_x = ball_x + BALL_SIZE / 2;
+ float ball_center_y = ball_y + BALL_SIZE / 2;
+ float dx = std::abs(ball_center_x - center_x);
+ float dy = std::abs(ball_center_y - center_y);
+
+ if (dx * BRICK_HEIGHT > dy * BRICK_WIDTH) {
+ ball_dx = -ball_dx;
+ } else {
+ ball_dy = -ball_dy;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool check_level_complete() {
+ return brick_count == 0;
+}
+
+void next_level() {
+ level++;
+ init_level();
+ draw_screen();
+}
+
+void handle_game_over() {
+ game_state = STATE_GAME_OVER;
+ gameover_initialized = false;
+ show_game_over();
+}
+
+void init_menu() {
+ cls();
+ background(COLOR_BACKGROUND);
+
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_blue = *ui::Theme::getInstance()->fg_blue;
+ auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
+
+ int16_t screen_width = ui::screen_width;
+ int16_t title_x = (screen_width - 17 * 8) / 2;
+ int16_t divider_width = 24 * 8;
+ int16_t divider_x = (screen_width - divider_width) / 2;
+ int16_t instruction_width = 22 * 8;
+ int16_t instruction_x = (screen_width - instruction_width) / 2;
+ int16_t prompt_width = 16 * 8;
+ prompt_x = (screen_width - prompt_width) / 2;
+
+ painter.fill_rectangle({0, 30, screen_width, 30}, Color::black());
+ painter.draw_string({title_x + 2, 42}, style_yellow, "*** BREAKOUT ***");
+ painter.draw_string({divider_x, 70}, style_blue, "========================");
+ painter.fill_rectangle({instruction_x - 5, 110, instruction_width + 10, 70}, Color::black());
+ painter.draw_rectangle({instruction_x - 5, 110, instruction_width + 10, 70}, Color::white());
+ painter.draw_string({instruction_x, 120}, style_cyan, " ROTARY: MOVE PADDLE");
+ painter.draw_string({instruction_x, 150}, style_cyan, " SELECT: START/LAUNCH");
+ painter.draw_string({divider_x, 190}, style_blue, "========================");
+
+ menu_initialized = true;
+}
+
+void show_menu() {
+ if (!menu_initialized) {
+ init_menu();
+ }
+
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+
+ if (++blink_counter >= 30) {
+ blink_counter = 0;
+ blink_state = !blink_state;
+
+ painter.fill_rectangle({prompt_x - 2, 228, 16 * 8 + 4, 20}, Color::black());
+
+ if (blink_state) {
+ painter.draw_string({prompt_x, 230}, style_red, "* PRESS SELECT *");
+ }
+ }
+}
+
+void init_game_over() {
+ cls();
+ background(COLOR_BACKGROUND);
+
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+
+ // int16_t screen_width = screen_width;
+ int16_t title_width = 9 * 8;
+ int16_t title_x = (screen_width - title_width) / 2;
+ int16_t score_text_width = (16 + std::to_string(score).length()) * 8;
+ int16_t score_x = (screen_width - score_text_width) / 2;
+
+ painter.draw_rectangle({20, 80, screen_width - 40, 160}, Color::red());
+ painter.draw_rectangle({22, 82, screen_width - 44, 156}, Color::white());
+
+ painter.draw_string({title_x, 100}, style_red, "GAME OVER");
+
+ painter.fill_rectangle({40, 140, screen_width - 80, 30}, Color::black());
+ painter.draw_rectangle({40, 140, screen_width - 80, 30}, Color::yellow());
+ painter.draw_string({score_x, 150}, style_yellow, " FINAL SCORE: " + std::to_string(score));
+
+ int16_t restart_width = 12 * 8;
+ restart_x = (screen_width - restart_width) / 2;
+
+ gameover_initialized = true;
+ gameover_blink_state = true;
+ gameover_blink_counter = 0;
+}
+
+void show_game_over() {
+ if (!gameover_initialized) {
+ init_game_over();
+ }
+
+ auto style_green = *ui::Theme::getInstance()->fg_green;
+
+ if (++gameover_blink_counter >= 30) {
+ gameover_blink_counter = 0;
+ gameover_blink_state = !gameover_blink_state;
+
+ painter.fill_rectangle({restart_x - 2, 198, 12 * 8 + 4, 20}, Color::black());
+
+ if (gameover_blink_state) {
+ painter.draw_string({restart_x, 200}, style_green, "PRESS SELECT");
+ }
+ }
+}
+
+void reset_game() {
+ level = 1;
+ score = 0;
+ lives = 3;
+ game_state = STATE_PLAYING;
+ init_level();
+ draw_screen();
+ gameover_initialized = false;
+ gameover_blink_state = true;
+ gameover_blink_counter = 0;
+}
+
+BreakoutView::BreakoutView(NavigationView& nav)
+ : nav_{nav} {
+ paddle_x = (screen_width - PADDLE_WIDTH) / 2;
+ ball_x = screen_width / 2;
+ add_children({&dummy});
+ game_timer.attach(&game_timer_check, 1.0 / 60.0);
+}
+
+void BreakoutView::on_show() {
+}
+
+void BreakoutView::paint(Painter& painter) {
+ (void)painter;
+
+ if (!initialized) {
+ initialized = true;
+ std::srand(LPC_RTC->CTIME0);
+ init_game();
+ }
+}
+
+void BreakoutView::frame_sync() {
+ check_game_timer();
+ set_dirty();
+}
+
+bool BreakoutView::on_encoder(const EncoderEvent delta) {
+ if (game_state == STATE_PLAYING) {
+ if (delta > 0) {
+ move_paddle_right();
+ set_dirty();
+ } else if (delta < 0) {
+ move_paddle_left();
+ set_dirty();
+ }
+ }
+ return true;
+}
+
+bool BreakoutView::on_key(const KeyEvent key) {
+ but_SELECT = (key == KeyEvent::Select);
+ but_LEFT = (key == KeyEvent::Left);
+ but_RIGHT = (key == KeyEvent::Right);
+
+ if (key == KeyEvent::Select) {
+ if (game_state == STATE_MENU) {
+ game_state = STATE_PLAYING;
+ reset_game();
+ } else if (game_state == STATE_PLAYING && ball_attached) {
+ launch_ball();
+ } else if (game_state == STATE_GAME_OVER) {
+ reset_game();
+ }
+ } else if (key == KeyEvent::Left) {
+ if (game_state == STATE_PLAYING) {
+ move_paddle_left();
+ }
+ } else if (key == KeyEvent::Right) {
+ if (game_state == STATE_PLAYING) {
+ move_paddle_right();
+ }
+ }
+
+ set_dirty();
+ return true;
+}
+
+} // namespace ui::external_app::breakout
\ No newline at end of file
diff --git a/firmware/application/external/breakout/ui_breakout.hpp b/firmware/application/external/breakout/ui_breakout.hpp
new file mode 100644
index 000000000..1ff303595
--- /dev/null
+++ b/firmware/application/external/breakout/ui_breakout.hpp
@@ -0,0 +1,154 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_BREAKOUT_H__
+#define __UI_BREAKOUT_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "event_m0.hpp"
+#include "message.hpp"
+#include "irq_controls.hpp"
+#include "random.hpp"
+#include "lpc43xx_cpp.hpp"
+#include "ui_widget.hpp"
+
+namespace ui::external_app::breakout {
+
+enum {
+ White,
+ Blue,
+ Yellow,
+ Purple,
+ Green,
+ Red,
+ Maroon,
+ Orange,
+ Black,
+};
+
+extern const Color pp_colors[];
+extern Painter painter;
+extern bool but_RIGHT;
+extern bool but_LEFT;
+extern bool but_SELECT;
+
+void cls();
+void background(int color);
+void fillrect(int x1, int y1, int x2, int y2, int color);
+void rect(int x1, int y1, int x2, int y2, int color);
+
+#define wait(x) chThdSleepMilliseconds(x * 1000)
+
+using Callback = void (*)(void);
+
+void check_game_timer();
+
+class Ticker {
+ public:
+ Ticker() = default;
+ void attach(Callback func, double delay_sec);
+ void detach();
+};
+
+#define PADDLE_WIDTH 40
+#define PADDLE_HEIGHT 10
+#define BALL_SIZE 8
+#define BRICK_WIDTH 20
+#define BRICK_HEIGHT 10
+#define BRICK_ROWS 5
+#define BRICK_COLS 10
+#define BRICK_GAP 2
+#define GAME_AREA_TOP 50
+#define GAME_AREA_BOTTOM 310
+#define PADDLE_Y (GAME_AREA_BOTTOM - PADDLE_HEIGHT)
+#define BALL_SPEED_INCREASE 0.1f
+
+#define STATE_MENU 0
+#define STATE_PLAYING 1
+#define STATE_GAME_OVER 3
+
+#define COLOR_BACKGROUND Black
+#define COLOR_PADDLE Blue
+#define COLOR_BALL White
+#define COLOR_BORDER White
+#define COLOR_BRICK_COLORS \
+ { Red, Orange, Yellow, Green, Purple }
+
+extern Ticker game_timer;
+
+extern int paddle_x;
+extern float ball_x;
+extern float ball_y;
+extern float ball_dx;
+extern float ball_dy;
+extern int score;
+extern int lives;
+extern int level;
+extern int game_state;
+extern bool initialized;
+extern bool ball_attached;
+extern unsigned int brick_count;
+extern bool bricks[BRICK_ROWS][BRICK_COLS];
+extern int brick_colors[BRICK_ROWS];
+
+void game_timer_check();
+void init_game();
+void init_level();
+void draw_screen();
+void draw_bricks();
+void draw_paddle();
+void draw_ball();
+void draw_score();
+void draw_lives();
+void draw_level();
+void draw_borders();
+void move_paddle_left();
+void move_paddle_right();
+void launch_ball();
+void update_game();
+void check_collisions();
+bool check_brick_collision(int row, int col);
+void handle_game_over();
+void show_menu();
+void show_game_over();
+bool check_level_complete();
+void next_level();
+void reset_game();
+
+class BreakoutView : public View {
+ public:
+ BreakoutView(NavigationView& nav);
+ void on_show() override;
+
+ std::string title() const override { return "Breakout"; }
+
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_encoder(const EncoderEvent event) override;
+ bool on_key(KeyEvent key) override;
+
+ private:
+ bool initialized = false;
+ NavigationView& nav_;
+
+ Button dummy{
+ {screen_width, 0, 0, 0},
+ ""};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+
+} // namespace ui::external_app::breakout
+
+#endif /* __UI_BREAKOUT_H__ */
\ No newline at end of file
diff --git a/firmware/application/external/cvs_spam/cvs_spam.cpp b/firmware/application/external/cvs_spam/cvs_spam.cpp
index 3486d7192..862fe6b52 100644
--- a/firmware/application/external/cvs_spam/cvs_spam.cpp
+++ b/firmware/application/external/cvs_spam/cvs_spam.cpp
@@ -81,8 +81,6 @@ void CVSSpamView::start_tx(const uint32_t id) {
return;
}
- const uint32_t sample_rate = 250000;
-
current_file = cvsfiles_dir / file_list[id].filename();
File capture_file;
@@ -99,20 +97,13 @@ void CVSSpamView::start_tx(const uint32_t id) {
return;
}
- auto metadata_path = get_metadata_path(current_file);
- auto metadata = read_metadata_file(metadata_path);
- if (!metadata) {
- metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
- }
-
- auto file_size = capture_file.size();
capture_file.close();
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
- baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
+ baseband::set_sample_rate(SAMPLE_RATE, get_oversample_rate(SAMPLE_RATE));
auto reader = std::make_unique();
if (auto error = reader->open(current_file)) {
@@ -120,10 +111,7 @@ void CVSSpamView::start_tx(const uint32_t id) {
"Cannot read C16 data.\n"
"Check file format/perms.\n"
"Rate: " +
- to_string_dec_uint(metadata->sample_rate) +
- "\n"
- "Size: " +
- to_string_dec_uint(file_size) +
+ to_string_dec_uint(SAMPLE_RATE) +
"\n"
"Error: " +
std::to_string(static_cast(error)));
@@ -132,9 +120,9 @@ void CVSSpamView::start_tx(const uint32_t id) {
progressbar.set_value(0);
- transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
- transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
- transmitter_model.set_target_frequency(metadata->center_frequency);
+ transmitter_model.set_sampling_rate(get_actual_sample_rate(SAMPLE_RATE));
+ transmitter_model.set_baseband_bandwidth(SAMPLE_RATE <= 500000 ? 1750000 : 2500000);
+ transmitter_model.set_target_frequency(TARGET_FREQUENCY);
transmitter_model.enable();
chThdSleepMilliseconds(100);
@@ -164,7 +152,6 @@ void CVSSpamView::start_random_tx() {
lfsr_v = lfsr_iterate(lfsr_v);
size_t random_index = lfsr_v % file_list.size();
- const uint32_t sample_rate = 250000;
current_file = cvsfiles_dir / file_list[random_index].filename();
File capture_file;
@@ -174,19 +161,13 @@ void CVSSpamView::start_random_tx() {
return;
}
- auto metadata_path = get_metadata_path(current_file);
- auto metadata = read_metadata_file(metadata_path);
- if (!metadata) {
- metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
- }
-
capture_file.close();
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
- baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
+ baseband::set_sample_rate(SAMPLE_RATE, get_oversample_rate(SAMPLE_RATE));
auto reader = std::make_unique();
if (auto error = reader->open(current_file)) {
@@ -196,9 +177,9 @@ void CVSSpamView::start_random_tx() {
progressbar.set_value(0);
- transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
- transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
- transmitter_model.set_target_frequency(metadata->center_frequency);
+ transmitter_model.set_sampling_rate(get_actual_sample_rate(SAMPLE_RATE));
+ transmitter_model.set_baseband_bandwidth(SAMPLE_RATE <= 500000 ? 1750000 : 2500000);
+ transmitter_model.set_target_frequency(TARGET_FREQUENCY);
transmitter_model.enable();
chThdSleepMilliseconds(100);
diff --git a/firmware/application/external/cvs_spam/cvs_spam.hpp b/firmware/application/external/cvs_spam/cvs_spam.hpp
index 77320d4c7..07396df9b 100644
--- a/firmware/application/external/cvs_spam/cvs_spam.hpp
+++ b/firmware/application/external/cvs_spam/cvs_spam.hpp
@@ -16,6 +16,9 @@ using namespace portapack;
namespace ui::external_app::cvs_spam {
+constexpr uint32_t SAMPLE_RATE = 250000;
+constexpr uint64_t TARGET_FREQUENCY = 433920000;
+
class CVSSpamView : public View {
public:
explicit CVSSpamView(NavigationView& nav);
@@ -50,7 +53,7 @@ class CVSSpamView : public View {
std::vector file_list{};
MenuView menu_view{
- {0, 0, 240, 180},
+ {0, 0, screen_width, 180},
true};
Text text_empty{
@@ -81,7 +84,7 @@ class CVSSpamView : public View {
LanguageHelper::currentMessages[LANG_STOP]};
ProgressBar progressbar{
- {0, 256, 240, 44}};
+ {0, 256, screen_width, 44}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
diff --git a/firmware/application/external/debug_pmem/main.cpp b/firmware/application/external/debug_pmem/main.cpp
new file mode 100644
index 000000000..69bc458ee
--- /dev/null
+++ b/firmware/application/external/debug_pmem/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_debug_pmem.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::debug_pmem {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::debug_pmem
+
+extern "C" {
+
+__attribute__((section(".external_app.app_debug_pmem.application_information"), used)) application_information_t _application_information_debug_pmem = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::debug_pmem::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "DebugPMem",
+ /*.bitmap_data = */ {
+ 0x54,
+ 0x15,
+ 0x54,
+ 0x15,
+ 0xFF,
+ 0x7F,
+ 0xFC,
+ 0x1F,
+ 0xFF,
+ 0x7F,
+ 0xCC,
+ 0x19,
+ 0xAF,
+ 0x7A,
+ 0x6C,
+ 0x1B,
+ 0xEF,
+ 0x7B,
+ 0xEC,
+ 0x1B,
+ 0xFF,
+ 0x7F,
+ 0xFC,
+ 0x1F,
+ 0xFF,
+ 0x7F,
+ 0x54,
+ 0x15,
+ 0x54,
+ 0x15,
+ 0x00,
+ 0x00,
+ },
+ /*.icon_color = */ ui::Color::cyan().v,
+ /*.menu_location = */ app_location_t::DEBUG,
+ /*.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
+};
+}
diff --git a/firmware/application/external/debug_pmem/ui_debug_pmem.cpp b/firmware/application/external/debug_pmem/ui_debug_pmem.cpp
new file mode 100644
index 000000000..8356569f2
--- /dev/null
+++ b/firmware/application/external/debug_pmem/ui_debug_pmem.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2018 Furrtek
+ *
+ * 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_debug_pmem.hpp"
+
+namespace ui::external_app::debug_pmem {
+
+// Dump pmem, receiver and transmitter models internals in human readable format
+bool DebugDumpView::debug_dump_func() {
+ std::string debug_dir = "DEBUG";
+ std::filesystem::path filename{};
+ File pmem_dump_file{};
+ // create new dump file name and DEBUG directory
+ ensure_directory(debug_dir);
+ filename = next_filename_matching_pattern(debug_dir + "/DEBUG_DUMP_????.TXT");
+ if (filename.empty()) {
+ dump_output.set("COULD NOT GET DUMP NAME !");
+ dump_output.set_style(ui::Theme::getInstance()->fg_red);
+ return false;
+ }
+ // dump data fo filename
+ auto error = pmem_dump_file.create(filename);
+ if (error) {
+ dump_output.set("ERROR DUMPING " + filename.filename().string() + " !");
+ dump_output.set_style(ui::Theme::getInstance()->fg_red);
+ return false;
+ }
+ pmem_dump_file.write_line("FW version: " VERSION_STRING);
+ pmem_dump_file.write_line("Ext APPS version req'd: 0x" + to_string_hex(VERSION_MD5));
+ pmem_dump_file.write_line("GCC version: " + to_string_dec_int(__GNUC__) + "." + to_string_dec_int(__GNUC_MINOR__) + "." + to_string_dec_int(__GNUC_PATCHLEVEL__));
+
+ // firmware checksum
+ pmem_dump_file.write_line("Firmware calculated checksum: 0x" + to_string_hex(simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE), 8));
+
+ // write persistent memory
+ pmem_dump_file.write_line("\n[Persistent Memory]");
+
+ // full variables
+ pmem_dump_file.write_line("structure_version: 0x" + to_string_hex(get_data_structure_version(), 8));
+ pmem_dump_file.write_line("target_frequency: " + to_string_dec_int(target_frequency()));
+ pmem_dump_file.write_line("correction_ppb: " + to_string_dec_int(correction_ppb()));
+ pmem_dump_file.write_line("modem_def_index: " + to_string_dec_uint(get_modem_def_index()));
+ pmem_dump_file.write_line("serial_format.data_bit: " + to_string_dec_uint(serial_format().data_bits));
+ pmem_dump_file.write_line("serial_format.parity: " + to_string_dec_uint(serial_format().parity));
+ pmem_dump_file.write_line("serial_format.stop_bits: " + to_string_dec_uint(serial_format().stop_bits));
+ pmem_dump_file.write_line("serial_format.bit_order: " + to_string_dec_uint(serial_format().bit_order));
+ pmem_dump_file.write_line("modem_bw: " + to_string_dec_int(modem_bw()));
+ pmem_dump_file.write_line("afsk_mark_freq: " + to_string_dec_int(afsk_mark_freq()));
+ pmem_dump_file.write_line("afsk_space_freq: " + to_string_dec_int(afsk_space_freq()));
+ pmem_dump_file.write_line("modem_baudrate: " + to_string_dec_int(modem_baudrate()));
+ pmem_dump_file.write_line("modem_repeat: " + to_string_dec_int(modem_repeat()));
+ pmem_dump_file.write_line("pocsag_last_address: " + to_string_dec_uint(pocsag_last_address()));
+ pmem_dump_file.write_line("pocsag_ignore_address: " + to_string_dec_uint(pocsag_ignore_address()));
+ pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(tone_mix()));
+ pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(config_cpld()));
+ pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(get_recon_config(), 16));
+ pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(recon_repeat_nb()));
+ pmem_dump_file.write_line("recon_repeat_gain: " + to_string_dec_int(recon_repeat_gain()));
+ pmem_dump_file.write_line("recon_repeat_delay: " + to_string_dec_int(recon_repeat_delay()));
+ pmem_dump_file.write_line("converter: " + to_string_dec_int(config_converter()));
+ pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(config_updown_converter()));
+ pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(config_freq_rx_correction_updown()));
+ pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(config_freq_tx_correction_updown()));
+ pmem_dump_file.write_line("lcd_normally_black: " + to_string_dec_uint(config_lcd_normally_black()));
+ pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(config_converter_freq()));
+ pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(config_freq_rx_correction()));
+ pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(config_freq_tx_correction()));
+ pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(encoder_dial_sensitivity()));
+ pmem_dump_file.write_line("encoder_rate_multiplier: " + to_string_dec_uint(encoder_rate_multiplier()));
+ pmem_dump_file.write_line("encoder_dial_direction: " + to_string_dec_uint(encoder_dial_direction())); // 0 = normal, 1 = reverse
+ pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(config_mode_storage_direct(), 8));
+ pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)config_dst().v, 8));
+ pmem_dump_file.write_line("fake_brightness_level: " + to_string_dec_uint(fake_brightness_level()));
+ pmem_dump_file.write_line("menu_color: 0x" + to_string_hex(menu_color().v, 4));
+ pmem_dump_file.write_line("touchscreen_threshold: " + to_string_dec_uint(touchscreen_threshold()));
+
+ // ui_config bits
+ const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
+ pmem_dump_file.write_line("ui_config clkout_freq: " + to_string_dec_uint(clkout_freq()));
+ pmem_dump_file.write_line("ui_config backlight_timer.timeout_enabled: " + to_string_dec_uint(backlight_timer.timeout_enabled()));
+ pmem_dump_file.write_line("ui_config backlight_timer.timeout_seconds: " + to_string_dec_uint(backlight_timer.timeout_seconds()));
+ pmem_dump_file.write_line("ui_config show_gui_return_icon: " + to_string_dec_uint(show_gui_return_icon()));
+ pmem_dump_file.write_line("ui_config load_app_settings: " + to_string_dec_uint(load_app_settings()));
+ pmem_dump_file.write_line("ui_config save_app_settings: " + to_string_dec_uint(save_app_settings()));
+ pmem_dump_file.write_line("ui_config disable_touchscreen: " + to_string_dec_uint(disable_touchscreen()));
+ pmem_dump_file.write_line("ui_config hide_clock: " + to_string_dec_uint(hide_clock()));
+ pmem_dump_file.write_line("ui_config clock_with_date: " + to_string_dec_uint(clock_with_date()));
+ pmem_dump_file.write_line("ui_config clkout_enabled: " + to_string_dec_uint(clkout_enabled()));
+ pmem_dump_file.write_line("ui_config apply_fake_brightness: " + to_string_dec_uint(apply_fake_brightness()));
+ pmem_dump_file.write_line("ui_config stealth_mode: " + to_string_dec_uint(stealth_mode()));
+ pmem_dump_file.write_line("ui_config config_login: " + to_string_dec_uint(config_login()));
+ pmem_dump_file.write_line("ui_config config_splash: " + to_string_dec_uint(config_splash()));
+
+ // ui_config2 bits
+ pmem_dump_file.write_line("ui_config2 hide_speaker: " + to_string_dec_uint(ui_hide_speaker()));
+ pmem_dump_file.write_line("ui_config2 hide_converter: " + to_string_dec_uint(ui_hide_converter()));
+ pmem_dump_file.write_line("ui_config2 hide_stealth: " + to_string_dec_uint(ui_hide_stealth()));
+ pmem_dump_file.write_line("ui_config2 hide_camera: " + to_string_dec_uint(ui_hide_camera()));
+ pmem_dump_file.write_line("ui_config2 hide_sleep: " + to_string_dec_uint(ui_hide_sleep()));
+ pmem_dump_file.write_line("ui_config2 hide_bias_tee: " + to_string_dec_uint(ui_hide_bias_tee()));
+ pmem_dump_file.write_line("ui_config2 hide_clock: " + to_string_dec_uint(ui_hide_clock()));
+ pmem_dump_file.write_line("ui_config2 hide_sd_card: " + to_string_dec_uint(ui_hide_sd_card()));
+ pmem_dump_file.write_line("ui_config2 hide_mute: " + to_string_dec_uint(ui_hide_mute()));
+ pmem_dump_file.write_line("ui_config2 hide_fake_brightness: " + to_string_dec_uint(ui_hide_fake_brightness()));
+ pmem_dump_file.write_line("ui_config2 hide_battery_icon: " + to_string_dec_uint(ui_hide_battery_icon()));
+ pmem_dump_file.write_line("ui_config2 hide_numeric_battery: " + to_string_dec_uint(ui_hide_numeric_battery()));
+ pmem_dump_file.write_line("ui_config2 theme_id: " + to_string_dec_uint(ui_theme_id()));
+ pmem_dump_file.write_line("ui_config2 override_batt_calc: " + to_string_dec_uint(ui_override_batt_calc()));
+ pmem_dump_file.write_line("ui_config2 button_repeat_delay: " + to_string_dec_uint(ui_button_repeat_delay()));
+ pmem_dump_file.write_line("ui_config2 button_repeat_speed: " + to_string_dec_uint(ui_button_repeat_speed()));
+ pmem_dump_file.write_line("ui_config2 button_long_press_delay: " + to_string_dec_uint(ui_button_long_press_delay()));
+ pmem_dump_file.write_line("ui_config2 battery_charge_hint: " + to_string_dec_uint(ui_battery_charge_hint()));
+
+ // misc_config bits
+ pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
+ pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
+ pmem_dump_file.write_line("misc_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
+ pmem_dump_file.write_line("misc_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
+ pmem_dump_file.write_line("misc_config config_disable_config_mode: " + to_string_dec_uint(config_disable_config_mode()));
+ pmem_dump_file.write_line("misc_config beep_on_packets: " + to_string_dec_int(beep_on_packets()));
+
+ // receiver_model
+ pmem_dump_file.write_line("\n[Receiver Model]");
+ pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(receiver_model.target_frequency()));
+ pmem_dump_file.write_line("frequency_step: " + to_string_dec_uint(receiver_model.frequency_step()));
+ pmem_dump_file.write_line("lna: " + to_string_dec_int(receiver_model.lna()));
+ pmem_dump_file.write_line("vga: " + to_string_dec_int(receiver_model.vga()));
+ pmem_dump_file.write_line("rf_amp: " + to_string_dec_int(receiver_model.rf_amp()));
+ pmem_dump_file.write_line("baseband_bandwidth: " + to_string_dec_uint(receiver_model.baseband_bandwidth()));
+ pmem_dump_file.write_line("sampling_rate: " + to_string_dec_uint(receiver_model.sampling_rate()));
+ switch (receiver_model.modulation()) {
+ case ReceiverModel::Mode::AMAudio:
+ pmem_dump_file.write_line("modulation: Mode::AMAudio");
+ break;
+ case ReceiverModel::Mode::NarrowbandFMAudio:
+ pmem_dump_file.write_line("modulation: Mode::NarrowbandFMAudio");
+ break;
+ case ReceiverModel::Mode::WidebandFMAudio:
+ pmem_dump_file.write_line("modulation: Mode::WidebandFMAudio");
+ break;
+ case ReceiverModel::Mode::WFMAudioAMApt:
+ pmem_dump_file.write_line("modulation: Mode::WFMAudioAMApt");
+ break;
+ case ReceiverModel::Mode::SpectrumAnalysis:
+ pmem_dump_file.write_line("modulation: Mode::SpectrumAnalysis");
+ break;
+ case ReceiverModel::Mode::Capture:
+ pmem_dump_file.write_line("modulation: Mode::Capture");
+ break;
+ case ReceiverModel::Mode::AMAudioFMApt:
+ pmem_dump_file.write_line("modulation: Mode::AMAudioFMApt");
+ break;
+ default:
+ pmem_dump_file.write_line("modulation: !!unknown mode!!");
+ break;
+ }
+ pmem_dump_file.write_line("headphone_volume.centibel: " + to_string_dec_int(receiver_model.headphone_volume().centibel()));
+ pmem_dump_file.write_line("normalized_headphone_volume: " + to_string_dec_uint(receiver_model.normalized_headphone_volume()));
+ pmem_dump_file.write_line("am_configuration: " + to_string_dec_uint(receiver_model.am_configuration()));
+ pmem_dump_file.write_line("nbfm_configuration: " + to_string_dec_uint(receiver_model.nbfm_configuration()));
+ pmem_dump_file.write_line("wfm_configuration: " + to_string_dec_uint(receiver_model.wfm_configuration()));
+
+ // transmitter_model
+ pmem_dump_file.write_line("\n[Transmitter Model]");
+ pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(transmitter_model.target_frequency()));
+ pmem_dump_file.write_line("rf_amp: " + to_string_dec_int(transmitter_model.rf_amp()));
+ pmem_dump_file.write_line("baseband_bandwidth: " + to_string_dec_uint(transmitter_model.baseband_bandwidth()));
+ pmem_dump_file.write_line("sampling_rate: " + to_string_dec_uint(transmitter_model.sampling_rate()));
+ pmem_dump_file.write_line("tx_gain: " + to_string_dec_int(transmitter_model.tx_gain()));
+ pmem_dump_file.write_line("channel_bandwidth: " + to_string_dec_uint(transmitter_model.channel_bandwidth()));
+ // on screen information
+ dump_output.set(filename.filename().string() + " DUMPED !");
+ dump_output.set_style(ui::Theme::getInstance()->fg_green);
+
+ return true;
+}
+
+DebugDumpView::DebugDumpView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&dump_output,
+ &button_exit});
+
+ debug_dump_func();
+
+ button_exit.on_select = [this](Button&) {
+ nav_.pop();
+ };
+}
+
+void DebugDumpView::focus() {
+ button_exit.focus();
+}
+
+} // namespace ui::external_app::debug_pmem
diff --git a/firmware/application/external/debug_pmem/ui_debug_pmem.hpp b/firmware/application/external/debug_pmem/ui_debug_pmem.hpp
new file mode 100644
index 000000000..f5ebedd97
--- /dev/null
+++ b/firmware/application/external/debug_pmem/ui_debug_pmem.hpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2018 Furrtek
+ *
+ * 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_DEBUG_PMEM_APP_H__
+#define __UI_DEBUG_PMEM_APP_H__
+
+#include "portapack_persistent_memory.hpp"
+#include "ui_flash_utility.hpp"
+
+using namespace portapack;
+using namespace portapack::persistent_memory;
+
+namespace ui::external_app::debug_pmem {
+
+class DebugDumpView : public View {
+ public:
+ DebugDumpView(NavigationView& nav);
+
+ void focus() override;
+
+ std::string title() const override { return "DebugPMem"; };
+ bool debug_dump_func();
+
+ private:
+ NavigationView& nav_;
+
+ Text dump_output{
+ {0 * 8, 19 * 8, screen_width, 16},
+ ""};
+
+ Button button_exit{
+ {22 * 8, 34 * 8, 8 * 8, 32},
+ "Exit"};
+};
+
+} // namespace ui::external_app::debug_pmem
+
+#endif /*__UI_DEBUG_PMEM_APP_H__*/
diff --git a/firmware/application/external/detector_rx/main.cpp b/firmware/application/external/detector_rx/main.cpp
new file mode 100644
index 000000000..2745f970e
--- /dev/null
+++ b/firmware/application/external/detector_rx/main.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_detector_rx.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::detector_rx {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::detector_rx
+
+extern "C" {
+
+__attribute__((section(".external_app.app_detector_rx.application_information"), used)) application_information_t _application_information_detector_rx = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::detector_rx::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Detector",
+ /*.bitmap_data = */
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x20,
+ 0x12,
+ 0x48,
+ 0x8A,
+ 0x51,
+ 0xCA,
+ 0x53,
+ 0xCA,
+ 0x53,
+ 0x8A,
+ 0x51,
+ 0x12,
+ 0x48,
+ 0x84,
+ 0x21,
+ 0xC0,
+ 0x03,
+ 0x40,
+ 0x02,
+ 0x60,
+ 0x06,
+ 0x20,
+ 0x04,
+ 0x30,
+ 0x0C,
+ 0xF0,
+ 0x0F},
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ // this has to be the biggest baseband used by the app. SPEC
+ /*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/detector_rx/ui_detector_rx.cpp b/firmware/application/external/detector_rx/ui_detector_rx.cpp
new file mode 100644
index 000000000..a1dd3b935
--- /dev/null
+++ b/firmware/application/external/detector_rx/ui_detector_rx.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2018 Furrtek
+ * Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
+ *
+ * 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_detector_rx.hpp"
+#include "ui_fileman.hpp"
+#include "ui_freqman.hpp"
+#include "baseband_api.hpp"
+#include "file.hpp"
+#include "oversample.hpp"
+#include "ui_font_fixed_8x16.hpp"
+
+using namespace portapack;
+using namespace tonekey;
+using portapack::memory::map::backup_ram;
+
+namespace ui::external_app::detector_rx {
+
+// Function to map the value from one range to another
+int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
+ return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
+}
+
+void DetectorRxView::focus() {
+ field_lna.focus();
+}
+
+DetectorRxView::~DetectorRxView() {
+ // reset performance counters request to default
+ shared_memory.request_m4_performance_counter = 0;
+ receiver_model.disable();
+ audio::output::stop();
+ baseband::shutdown();
+}
+
+void DetectorRxView::on_timer() {
+ freq_index++;
+ if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
+ if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
+ if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
+
+ if (freq_mode == 2)
+ freq_ = remotes_monitoring_frequencies_hz[freq_index];
+ else if (freq_mode == 1)
+ freq_ = lora_monitoring_frequencies_hz[freq_index];
+ else
+ freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
+ receiver_model.set_target_frequency(freq_);
+}
+
+DetectorRxView::DetectorRxView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({
+ &labels,
+ &field_lna,
+ &field_vga,
+ &field_rf_amp,
+ &field_volume,
+ &text_frequency,
+ &freq_stats_rssi,
+ &freq_stats_db,
+ &text_beep_squelch,
+ &field_beep_squelch,
+ &rssi,
+ &rssi_graph,
+ &field_mode,
+ });
+
+ // activate vertical bar mode
+ rssi.set_vertical_rssi(true);
+
+ freq_ = receiver_model.target_frequency();
+
+ field_beep_squelch.set_value(beep_squelch);
+ field_beep_squelch.on_change = [this](int32_t v) {
+ beep_squelch = v;
+ };
+
+ rssi_graph.set_nb_columns(256);
+
+ change_mode();
+ rssi.set_peak(true, 3000);
+
+ // FILL STEP OPTIONS
+ freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
+ freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
+
+ field_mode.on_change = [this](size_t, int32_t value) {
+ freq_mode = value;
+ freq_index = 0;
+ switch (value) {
+ case 1: // Lora
+ text_frequency.set(" 433, 868, 915 Mhz");
+ break;
+ case 2: // Remotes
+ text_frequency.set(" 433, 315 Mhz");
+ break;
+ default:
+ case 0: // TETRA UP
+ text_frequency.set(" 380-390 Mhz");
+ break;
+ }
+ };
+}
+
+void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
+ static int16_t last_max_db = 0;
+ static uint8_t last_min_rssi = 0;
+ static uint8_t last_avg_rssi = 0;
+ static uint8_t last_max_rssi = 0;
+
+ rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
+
+ // refresh db
+ if (last_max_db != statistics.max_db) {
+ last_max_db = statistics.max_db;
+ freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
+ rssi.set_db(statistics.max_db);
+ }
+ // refresh rssi
+ if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
+ last_min_rssi = rssi_graph.get_graph_min();
+ last_avg_rssi = rssi_graph.get_graph_avg();
+ last_max_rssi = rssi_graph.get_graph_max();
+ freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
+ }
+
+ if (statistics.max_db > beep_squelch) {
+ baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
+ }
+
+} /* on_statistic_updates */
+
+size_t DetectorRxView::change_mode() {
+ audio::output::stop();
+ receiver_model.disable();
+ baseband::shutdown();
+
+ audio_sampling_rate = audio::Rate::Hz_24000;
+ baseband::run_image(portapack::spi_flash::image_tag_capture);
+ receiver_model.set_modulation(ReceiverModel::Mode::Capture);
+
+ baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
+ // The radio needs to know the effective sampling rate.
+ auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
+ receiver_model.set_sampling_rate(actual_sampling_rate);
+ receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
+
+ audio::set_rate(audio_sampling_rate);
+ audio::output::start();
+ receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
+
+ receiver_model.enable();
+
+ return 0;
+}
+
+} // namespace ui::external_app::detector_rx
diff --git a/firmware/application/external/detector_rx/ui_detector_rx.hpp b/firmware/application/external/detector_rx/ui_detector_rx.hpp
new file mode 100644
index 000000000..4814f297f
--- /dev/null
+++ b/firmware/application/external/detector_rx/ui_detector_rx.hpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2018 Furrtek
+ * Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
+ *
+ * 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_DETECTOR_RX
+#define _UI_DETECTOR_RX
+
+#include "analog_audio_app.hpp"
+#include "app_settings.hpp"
+#include "audio.hpp"
+#include "baseband_api.hpp"
+#include "file.hpp"
+#include "freqman_db.hpp"
+#include "portapack_persistent_memory.hpp"
+#include "radio_state.hpp"
+#include "receiver_model.hpp"
+#include "string_format.hpp"
+#include "ui.hpp"
+#include "ui_mictx.hpp"
+#include "ui_receiver.hpp"
+#include "ui_spectrum.hpp"
+
+namespace ui::external_app::detector_rx {
+
+#define DETECTOR_BW 750000
+
+class DetectorRxView : public View {
+ public:
+ DetectorRxView(NavigationView& nav);
+ ~DetectorRxView();
+
+ void focus() override;
+
+ std::string title() const override { return "Detector RX"; };
+
+ private:
+ NavigationView& nav_;
+
+ RxRadioState radio_state_{};
+
+ int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
+ size_t change_mode();
+ void on_statistics_update(const ChannelStatistics& statistics);
+ void set_display_freq(int64_t freq);
+ void on_timer();
+
+ uint8_t freq_index = 0;
+ rf::Frequency freq_ = {433920000};
+ int32_t beep_squelch = 0;
+ audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
+ uint8_t freq_mode = 0;
+
+ app_settings::SettingsManager settings_{
+ "rx_detector",
+ app_settings::Mode::RX,
+ {
+ {"beep_squelch"sv, &beep_squelch},
+ }};
+
+ Labels labels{
+ {{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground}};
+
+ LNAGainField field_lna{
+ {UI_POS_X(4), UI_POS_Y(0)}};
+
+ VGAGainField field_vga{
+ {UI_POS_X(11), UI_POS_Y(0)}};
+
+ RFAmpField field_rf_amp{
+ {UI_POS_X(18), UI_POS_Y(0)}};
+
+ AudioVolumeField field_volume{
+ {UI_POS_X(24), UI_POS_Y(0)}};
+
+ OptionsField field_mode{
+ {UI_POS_X(0), UI_POS_Y(1)},
+ 9,
+ {
+ {"TETRA UP", 0},
+ {"Lora", 1},
+ {"Remotes", 2},
+ }};
+
+ Text text_frequency{
+ {UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
+ ""};
+
+ Text text_beep_squelch{
+ {UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
+ "Bip>"};
+
+ NumberField field_beep_squelch{
+ {UI_POS_X_RIGHT(5), UI_POS_Y(2)},
+ 4,
+ {-100, 20},
+ 1,
+ ' ',
+ };
+
+ // RSSI: XX/XX/XXX
+ Text freq_stats_rssi{
+ {UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
+ };
+
+ // Power: -XXX db
+ Text freq_stats_db{
+ {UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
+ };
+
+ RSSIGraph rssi_graph{
+ {UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
+ };
+
+ RSSI rssi{
+ {UI_POS_X_RIGHT(5), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(6)},
+ };
+
+ MessageHandlerRegistration message_handler_stats{
+ Message::ID::ChannelStatistics,
+ [this](const Message* const p) {
+ this->on_statistics_update(static_cast(p)->statistics);
+ }};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->on_timer();
+ }};
+
+ const std::vector remotes_monitoring_frequencies_hz = {
+ // Around 315 MHz (common for older remotes, key fobs in some regions)
+ // Window centered on 315 MHz, covers 314.625 - 315.375 MHz
+ 315000000,
+
+ // Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
+ // Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
+ 433920000,
+ };
+ const std::vector lora_monitoring_frequencies_hz = {
+ // EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
+ // Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
+ 433375000, // Covers 433.000 - 433.750 MHz
+ 434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
+ 434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
+
+ // EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
+ // Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
+ 867375000, // Covers 867.000 - 867.750 MHz
+ 868125000, // Covers 867.750 - 868.500 MHz
+ 868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
+
+ // US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
+ // Providing a few sample windows around the 915 MHz area with 750kHz steps.
+ // This band is wide; a full scan would require many more frequencies.
+ 914250000, // Covers 913.875 - 914.625 MHz
+ 915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
+ 915750000, // Covers 915.375 - 916.125 MHz
+ };
+ const std::vector tetra_uplink_monitoring_frequencies_hz = {
+ // Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
+ // First center: 380,000,000 + 375,000 = 380,375,000 Hz
+ // Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
+ 380375000, // Covers 380.000 - 380.750 MHz
+ 381125000, // Covers 380.750 - 381.500 MHz
+ 381875000, // Covers 381.500 - 382.250 MHz
+ 382625000, // Covers 382.250 - 383.000 MHz
+ 383375000, // Covers 383.000 - 383.750 MHz
+ 384125000, // Covers 383.750 - 384.500 MHz
+ 384875000, // Covers 384.500 - 385.250 MHz
+ 385625000, // Covers 385.250 - 386.000 MHz
+ 386375000, // Covers 386.000 - 386.750 MHz
+ 387125000, // Covers 386.750 - 387.500 MHz
+ 387875000, // Covers 387.500 - 388.250 MHz
+ 388625000, // Covers 388.250 - 389.000 MHz
+ 389375000, // Covers 389.000 - 389.750 MHz
+ 390125000, // Covers 389.750 - 390.500 MHz
+ };
+};
+
+} // namespace ui::external_app::detector_rx
+
+#endif
diff --git a/firmware/application/external/dinogame/main.cpp b/firmware/application/external/dinogame/main.cpp
new file mode 100644
index 000000000..66126b7d4
--- /dev/null
+++ b/firmware/application/external/dinogame/main.cpp
@@ -0,0 +1,58 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ *
+ * Chrome Dino Game for Portapack Mayhem
+ * Based on the original DinoGame by various contributors
+ */
+
+#include "ui.hpp"
+#include "ui_dinogame.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::dinogame {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::dinogame
+
+extern "C" {
+
+__attribute__((section(".external_app.app_dinogame.application_information"), used)) application_information_t _application_information_dinogame = {
+ (uint8_t*)0x00000000,
+ ui::external_app::dinogame::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+
+ "Dino Game",
+ {
+ // Cactus icon 16x16
+ 0x00, 0x00, // ................
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x98, 0x19, // ...##..##..##...
+ 0x98, 0x19, // ...##..##..##...
+ 0x98, 0x19, // ...##..##..##...
+ 0x98, 0x19, // ...##..##..##...
+ 0xF8, 0x1F, // ...##########...
+ 0xF0, 0x0F, // ....########....
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x80, 0x01, // .......##.......
+ 0x00, 0x00, // ................
+ },
+ ui::Color::green().v,
+ app_location_t::GAMES,
+ -1,
+
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+}
\ No newline at end of file
diff --git a/firmware/application/external/dinogame/sprites/dino.h b/firmware/application/external/dinogame/sprites/dino.h
new file mode 100644
index 000000000..d9cb148c0
--- /dev/null
+++ b/firmware/application/external/dinogame/sprites/dino.h
@@ -0,0 +1,447 @@
+/*
+ standing - 34x36
+ ducking - 46x24
+*/
+const uint16_t dino_default[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
+ 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t dino_leftstep[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
+ 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0470 (1136)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0490 (1168)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t dino_rightstep[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t dino_ducking_leftstep[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
+ 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02B0 (688)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x02E0 (736)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x03C0 (960)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t dino_ducking_rightstep[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
+ 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02E0 (736)
+ 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0310 (784)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0340 (832)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t dino_gameover[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0xFFFF, 0x528A, // 0x00A0 (160)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x00C0 (192)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
+ 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
+ 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
\ No newline at end of file
diff --git a/firmware/application/external/dinogame/sprites/pterodactyl.h b/firmware/application/external/dinogame/sprites/pterodactyl.h
new file mode 100644
index 000000000..39f0485f4
--- /dev/null
+++ b/firmware/application/external/dinogame/sprites/pterodactyl.h
@@ -0,0 +1,121 @@
+// 34x27
+
+const uint16_t pterodactyl_upflop[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0030 (48)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x00E0 (224)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
+ 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+
+const uint16_t pterodactyl_downflop[] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00E0 (224)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0100 (256)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
+ 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
+ 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
+ 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
+ 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0300 (768)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0320 (800)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0360 (864)
+ 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
+ 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
\ No newline at end of file
diff --git a/firmware/application/external/dinogame/ui_dinogame.cpp b/firmware/application/external/dinogame/ui_dinogame.cpp
new file mode 100644
index 000000000..f9c456637
--- /dev/null
+++ b/firmware/application/external/dinogame/ui_dinogame.cpp
@@ -0,0 +1,808 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+/*
+ * Chrome Dino Game for Portapack Mayhem
+ * Based on the original DinoGame by various contributors
+ */
+
+#include "ui_dinogame.hpp"
+
+namespace ui::external_app::dinogame {
+
+#define PROGMEM
+#include "sprites/dino.h"
+#include "sprites/pterodactyl.h"
+
+#undef PROGMEM
+
+// Global variables
+Ticker game_timer;
+Painter painter;
+static Callback game_update_callback = nullptr;
+static uint32_t game_update_timeout = 0;
+static uint32_t game_update_counter = 0;
+static DinoGameView* current_instance = nullptr;
+
+const Color pp_colors[] = {
+ Color::white(),
+ Color::blue(),
+ Color::yellow(),
+ Color::purple(),
+ Color::green(),
+ Color::red(),
+ Color::magenta(),
+ Color::orange(),
+ Color::black(),
+};
+
+// Drawing functions
+void cls() {
+ painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
+}
+
+void fillrect(int x1, int y1, int x2, int y2, int color) {
+ painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void rect(int x1, int y1, int x2, int y2, int color) {
+ painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+// Timer implementation
+void check_game_timer() {
+ if (game_update_callback) {
+ if (++game_update_counter >= game_update_timeout) {
+ game_update_counter = 0;
+ game_update_callback();
+ }
+ }
+}
+
+void Ticker::attach(Callback func, double delay_sec) {
+ game_update_callback = func;
+ game_update_timeout = delay_sec * 60;
+}
+
+void Ticker::detach() {
+ game_update_callback = nullptr;
+}
+
+// String helper
+std::string DinoGameView::score_to_string(uint32_t score) {
+ std::string score_s = std::to_string(score);
+ if (score_s.length() < 5) {
+ std::string temp = "";
+ for (uint8_t i = 0; i < 5 - score_s.length(); ++i) temp += "0";
+ temp += score_s;
+ score_s = temp;
+ }
+ return score_s;
+}
+
+// Game timer callback
+void game_timer_check() {
+ if (current_instance) {
+ if (current_instance->game_state == GameState::PLAYING) {
+ current_instance->game_loop();
+ } else if (current_instance->game_state == GameState::MENU) {
+ current_instance->show_menu();
+ }
+ }
+}
+
+DinoGameView::DinoGameView(NavigationView& nav)
+ : nav_{nav}, bird_info{}, game_timer{} {
+ add_children({&dummy, &button_difficulty});
+ current_instance = this;
+ game_timer.attach(&game_timer_check, 1.0 / 60.0);
+
+ button_difficulty.on_select = [this](Button&) {
+ easy_mode = !easy_mode;
+ button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
+ };
+}
+
+void DinoGameView::on_show() {
+}
+
+void DinoGameView::paint(Painter& painter) {
+ (void)painter;
+
+ if (!initialized) {
+ initialized = true;
+ std::srand(LPC_RTC->CTIME0);
+ init_game();
+ }
+}
+
+void DinoGameView::frame_sync() {
+ check_game_timer();
+ set_dirty();
+}
+
+void DinoGameView::init_game() {
+ game_state = GameState::MENU;
+ menu_initialized = false;
+ blink_state = true;
+ blink_counter = 0;
+ new_game();
+}
+
+void DinoGameView::new_game() {
+ steps = 0;
+ score = 0;
+ collided = false;
+ ducking = false;
+ duck_timer = 0;
+ displayedGameOver = false;
+ bird_info.inGame = false;
+ bird_info.x_offset = 320;
+ bird_info.y_offset = 0;
+ bird_info.y_velocity = 0;
+ jumping = false;
+ falling = false;
+ jumpHeight = 0;
+ last_dino_y = DINO_Y;
+ last_bird_x = -1;
+ last_bird_y = -1;
+ last_ducking = false;
+ last_runstate = false;
+ ground_offset = 0;
+ speed_modifier = 0;
+ runstate = false;
+ score_drawn = false;
+ last_score = 999999;
+ obstacle_spawn_timer = 100; // Initial delay before first obstacle
+
+ // Initialize obstacles
+ for (int i = 0; i < MAX_OBSTACLES; i++) {
+ obstacles[i].active = false;
+ obstacles[i].x = -100;
+ obstacles[i].last_x = -100;
+ }
+
+ cls();
+}
+
+void DinoGameView::show_menu() {
+ if (!menu_initialized) {
+ cls();
+
+ auto style = *ui::Theme::getInstance()->fg_medium;
+ auto style_title = *ui::Theme::getInstance()->fg_light;
+
+ // Draw title
+ painter.draw_string({80, 60}, style_title, "DINO GAME");
+
+ // Draw instructions
+ painter.draw_string({45, 130}, style, "SELECT: Jump/Start");
+ painter.draw_string({65, 150}, style, "DOWN: Duck");
+ painter.draw_string({50, 170}, style, "Avoid obstacles!");
+
+ // Draw high score
+ draw_high_score();
+
+ menu_initialized = true;
+ }
+
+ // Show difficulty button
+ button_difficulty.hidden(false);
+
+ // Animate the menu dino
+ bool menu_run_frame = (blink_counter / 15) % 2;
+
+ // Clear previous dino position
+ fillrect(103, 90, 103 + DINO_WIDTH, 90 + DINO_HEIGHT, Black);
+
+ // Draw animated dino
+ draw_dino_at(103, 90, false, menu_run_frame);
+
+ // Blinking start prompt
+ auto style_prompt = *ui::Theme::getInstance()->fg_light;
+ if (++blink_counter >= 30) {
+ blink_counter = 0;
+ blink_state = !blink_state;
+
+ painter.fill_rectangle({55, 258, 130, 20}, Color::black());
+ if (blink_state) {
+ painter.draw_string({55, 260}, style_prompt, "* PRESS SELECT *");
+ }
+ }
+}
+
+void DinoGameView::show_game_over() {
+ if (!displayedGameOver) {
+ displayedGameOver = true;
+
+ // Clear the last normal dino position
+ if (last_ducking) {
+ fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
+ } else {
+ fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
+ }
+
+ // Draw the game over dino sprite
+ draw_dino_sprite(DINO_X, DINO_Y, dino_gameover);
+
+ auto style = *ui::Theme::getInstance()->fg_light;
+ auto style_score = *ui::Theme::getInstance()->fg_medium;
+
+ // Game over text
+ painter.draw_string({85, 70}, style, "GAME OVER");
+
+ // Show final score
+ std::string score_text = "SCORE: " + score_to_string(score);
+ int score_x = (240 - score_text.length() * 8) / 2;
+ painter.draw_string({score_x, 90}, style_score, score_text);
+
+ painter.draw_string({65, 110}, style, "SELECT TO RETRY");
+
+ // Update high score
+ if (score > highScore) {
+ highScore = score;
+ painter.draw_string({55, 130}, style, "NEW HIGH SCORE!");
+ }
+ }
+}
+
+void DinoGameView::game_loop() {
+ button_difficulty.hidden(true);
+
+ if (collided && game_state == GameState::PLAYING) {
+ game_state = GameState::GAME_OVER;
+ show_game_over();
+ return;
+ }
+
+ // Update ground animation
+ ground_offset = (ground_offset + GAME_SPEED_BASE + speed_modifier) % 20;
+
+ // Clear only the game area (not the whole screen to reduce flicker)
+ draw_ground();
+
+ // Update and draw obstacles
+ update_obstacles();
+
+ // Manage bird
+ manage_bird();
+
+ // Handle jumping
+ if (jumping) {
+ if (!falling) jumpHeight += JUMP_SPEED + speed_modifier;
+ if (jumpHeight > JUMP_MAX_HEIGHT && !falling) falling = true;
+ if (falling) jumpHeight -= JUMP_SPEED + speed_modifier;
+ if (jumpHeight < 0) {
+ falling = false;
+ jumping = false;
+ jumpHeight = 0;
+ }
+ }
+
+ // Handle auto-stand from duck
+ if (ducking && duck_timer > 0) {
+ duck_timer--;
+ if (duck_timer == 0) {
+ stand();
+ }
+ }
+
+ // Update run animation
+ if (get_steps() % (10 - speed_modifier) == 0) runstate = !runstate;
+
+ // Draw dino with minimal redraw
+ int current_dino_y = jumping ? (DINO_Y - jumpHeight) : (ducking ? DINO_DUCK_Y : DINO_Y);
+
+ // Clear old dino position more precisely
+ if (current_dino_y != last_dino_y || runstate != last_runstate || ducking != last_ducking) {
+ // Clear based on last state
+ if (last_ducking) {
+ fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
+ } else {
+ fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
+ }
+ }
+
+ // Draw dino at new position
+ if (jumping) {
+ draw_dino_sprite(DINO_X, current_dino_y, dino_default);
+ } else if (ducking) {
+ if (runstate)
+ draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_leftstep);
+ else
+ draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_rightstep);
+ } else {
+ if (runstate)
+ draw_dino_sprite(DINO_X, current_dino_y, dino_leftstep);
+ else
+ draw_dino_sprite(DINO_X, current_dino_y, dino_rightstep);
+ }
+
+ // Update last state
+ last_dino_y = current_dino_y;
+ last_ducking = ducking;
+ last_runstate = runstate;
+
+ // Check collisions
+ check_collision();
+
+ // Update score
+ score = get_steps() / 10;
+ if (score % 100 == 0 && score > 0 && get_steps() % 1000 == 0) {
+ if (speed_modifier < 5) speed_modifier++;
+ }
+
+ step();
+ draw_current_score();
+ draw_high_score();
+}
+
+void DinoGameView::draw_ground() {
+ int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
+
+ // Clear ground area
+ fillrect(0, ground_y, 320, ground_y + GROUND_HEIGHT, Black);
+
+ // Draw ground line
+ painter.draw_hline({0, ground_y}, 320, Color::white());
+ painter.draw_hline({0, ground_y + 1}, 320, Color::dark_grey());
+
+ // Draw ground texture
+ for (int x = -ground_offset; x < 320; x += 20) {
+ painter.draw_hline({x, ground_y + 3}, 10, Color::dark_grey());
+ painter.draw_hline({x + 5, ground_y + 5}, 5, Color::dark_grey());
+ }
+}
+
+void DinoGameView::update_obstacles() {
+ // Move obstacles
+ for (int i = 0; i < MAX_OBSTACLES; i++) {
+ if (obstacles[i].active) {
+ // Clear old position
+ if (obstacles[i].last_x != obstacles[i].x && obstacles[i].last_x < 320) {
+ clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
+ }
+
+ obstacles[i].last_x = obstacles[i].x;
+ obstacles[i].x -= GAME_SPEED_BASE + speed_modifier;
+
+ // Remove off-screen obstacles
+ if (obstacles[i].x < -50) {
+ obstacles[i].active = false;
+ clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
+ } else {
+ // Draw obstacle at new position
+ draw_obstacle(obstacles[i]);
+ }
+ }
+ }
+
+ // Decrement spawn timer
+ if (obstacle_spawn_timer > 0) {
+ obstacle_spawn_timer -= GAME_SPEED_BASE + speed_modifier;
+ }
+
+ // Check if we should spawn a new obstacle
+ if (obstacle_spawn_timer <= 0) {
+ // Try to spawn an obstacle
+ bool spawned = false;
+ for (int i = 0; i < MAX_OBSTACLES; i++) {
+ if (!obstacles[i].active) {
+ obstacles[i].active = true;
+ obstacles[i].x = 320;
+ obstacles[i].last_x = 320;
+ obstacles[i].type = rand() % 4;
+
+ // Set obstacle dimensions based on type
+ switch (obstacles[i].type) {
+ case 0: // Small cactus
+ obstacles[i].width = 15;
+ obstacles[i].height = 25;
+ break;
+ case 1: // Large cactus
+ obstacles[i].width = 20;
+ obstacles[i].height = 35;
+ break;
+ case 2: // Double cactus
+ obstacles[i].width = 35;
+ obstacles[i].height = 30;
+ break;
+ case 3: // Triple cactus
+ obstacles[i].width = 45;
+ obstacles[i].height = 28;
+ break;
+ }
+
+ spawned = true;
+ break;
+ }
+ }
+
+ if (spawned) {
+ // Set timer for next obstacle with random variation
+ obstacle_spawn_timer = MIN_OBSTACLE_DISTANCE + (rand() % (MAX_OBSTACLE_DISTANCE - MIN_OBSTACLE_DISTANCE));
+ }
+ }
+}
+
+void DinoGameView::clear_obstacle_area(int x, int width, int height) {
+ int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
+ // Only clear the obstacle area, not the entire vertical space
+ fillrect(x - 5, ground_y - height - 5, x + width + 5, ground_y, Black);
+}
+
+void DinoGameView::draw_obstacle(const SimpleObstacle& obstacle) {
+ if (!obstacle.active) return;
+
+ int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
+ int y = ground_y - obstacle.height;
+
+ // Draw cactus with green color
+ switch (obstacle.type) {
+ case 0: // Small single cactus
+ fillrect(obstacle.x + 5, y, obstacle.x + 10, ground_y, Green);
+ fillrect(obstacle.x, y + 8, obstacle.x + 5, y + 15, Green);
+ fillrect(obstacle.x + 10, y + 12, obstacle.x + 15, y + 19, Green);
+ // Add darker edges for definition
+ painter.draw_vline({obstacle.x + 4, y + 8}, 7, Color::dark_green());
+ painter.draw_vline({obstacle.x + 10, y + 12}, 7, Color::dark_green());
+ break;
+
+ case 1: // Large single cactus
+ fillrect(obstacle.x + 7, y, obstacle.x + 13, ground_y, Green);
+ fillrect(obstacle.x, y + 10, obstacle.x + 7, y + 20, Green);
+ fillrect(obstacle.x + 13, y + 15, obstacle.x + 20, y + 25, Green);
+ fillrect(obstacle.x + 5, y + 5, obstacle.x + 7, y + 12, Green);
+ fillrect(obstacle.x + 13, y + 8, obstacle.x + 15, y + 15, Green);
+ // Add darker edges
+ painter.draw_vline({obstacle.x + 6, y}, ground_y - y, Color::dark_green());
+ painter.draw_vline({obstacle.x + 13, y}, ground_y - y, Color::dark_green());
+ break;
+
+ case 2: // Double cactus
+ fillrect(obstacle.x + 5, y + 5, obstacle.x + 10, ground_y, Green);
+ fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
+ fillrect(obstacle.x, y + 12, obstacle.x + 5, y + 18, Green);
+ fillrect(obstacle.x + 10, y + 15, obstacle.x + 15, y + 22, Green);
+ fillrect(obstacle.x + 25, y + 10, obstacle.x + 30, y + 17, Green);
+ // Darker outlines
+ painter.draw_vline({obstacle.x + 4, y + 5}, ground_y - y - 5, Color::dark_green());
+ painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
+ break;
+
+ case 3: // Triple cactus
+ fillrect(obstacle.x + 5, y + 8, obstacle.x + 10, ground_y, Green);
+ fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
+ fillrect(obstacle.x + 35, y + 5, obstacle.x + 40, ground_y, Green);
+ fillrect(obstacle.x, y + 15, obstacle.x + 5, y + 20, Green);
+ fillrect(obstacle.x + 25, y + 8, obstacle.x + 30, y + 15, Green);
+ fillrect(obstacle.x + 40, y + 12, obstacle.x + 45, y + 18, Green);
+ // Darker outlines
+ painter.draw_vline({obstacle.x + 4, y + 8}, ground_y - y - 8, Color::dark_green());
+ painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
+ painter.draw_vline({obstacle.x + 34, y + 5}, ground_y - y - 5, Color::dark_green());
+ break;
+ }
+}
+
+void DinoGameView::manage_bird() {
+ if (!bird_info.inGame) {
+ // Only spawn bird if no obstacles are too close
+ bool obstacle_nearby = false;
+ for (int i = 0; i < MAX_OBSTACLES; i++) {
+ if (obstacles[i].active && obstacles[i].x > 200) {
+ obstacle_nearby = true;
+ break;
+ }
+ }
+
+ // Randomly spawn bird if no nearby obstacles
+ if (!obstacle_nearby && rand() % 400 == 0 && get_steps() > 100) {
+ bird_info.inGame = true;
+ bird_info.x_offset = 320;
+ bird_info.y_offset = 0;
+ bird_info.y_velocity = easy_mode ? 0 : (rand() % 3) - 1; // No vertical movement in easy mode
+ bird_info.y_position = BirdPosition::DOWN; // Always spawn at standing height in easy mode
+ }
+ } else {
+ // Calculate bird Y position
+ int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
+ int current_y = base_y + bird_info.y_offset;
+
+ // Clear previous bird position
+ if (last_bird_x < 320 && last_bird_x >= -BIRD_WIDTH) {
+ int clear_start_x = last_bird_x;
+ int clear_end_x = last_bird_x + BIRD_WIDTH;
+
+ if (clear_start_x < 0) clear_start_x = 0;
+ if (clear_end_x > 320) clear_end_x = 320;
+
+ if (clear_end_x > clear_start_x) {
+ fillrect(clear_start_x, last_bird_y, clear_end_x, last_bird_y + BIRD_HEIGHT, Black);
+ }
+ }
+
+ // Move bird
+ bird_info.x_offset -= GAME_SPEED_BASE + speed_modifier + 1;
+
+ // Update vertical position only in hard mode
+ if (!easy_mode) {
+ bird_info.y_offset += bird_info.y_velocity;
+ if (rand() % 30 == 0) {
+ bird_info.y_velocity = (rand() % 3) - 1;
+ }
+
+ // Keep bird within bounds
+ if (current_y < GAME_AREA_TOP + 10) {
+ current_y = GAME_AREA_TOP + 10;
+ bird_info.y_offset = current_y - base_y;
+ bird_info.y_velocity = 1;
+ } else if (current_y > GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10) {
+ current_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10;
+ bird_info.y_offset = current_y - base_y;
+ bird_info.y_velocity = -1;
+ }
+ } else {
+ // In easy mode, keep bird at perfect ducking height
+ // Bird should hit standing dino but miss ducking dino
+ current_y = DINO_Y - 10; // Position bird just above ducking height
+ }
+
+ // Animate wings
+ if (get_steps() % 15 == 0) bird_info.flop = !bird_info.flop;
+
+ // Draw bird only if any part is visible
+ if (bird_info.x_offset > -BIRD_WIDTH && bird_info.x_offset < 320) {
+ if (bird_info.flop) {
+ draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_upflop);
+ } else {
+ draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_downflop);
+ }
+ }
+
+ // Update last position
+ last_bird_x = bird_info.x_offset;
+ last_bird_y = current_y;
+
+ // Remove bird only when completely off screen
+ if (bird_info.x_offset < -BIRD_WIDTH - 5) {
+ bird_info.inGame = false;
+ last_bird_x = -1;
+ last_bird_y = -1;
+ }
+ }
+}
+
+void DinoGameView::draw_dino_sprite(int x, int y, const uint16_t* sprite) {
+ int height, width;
+
+ // Determine dimensions based on which sprite we're drawing
+ if (sprite == dino_ducking_leftstep || sprite == dino_ducking_rightstep) {
+ height = DINO_DUCK_HEIGHT;
+ width = DINO_DUCK_WIDTH;
+ } else {
+ height = DINO_HEIGHT;
+ width = DINO_WIDTH;
+ }
+
+ for (int dy = 0; dy < height; dy++) {
+ int run_start = -1;
+ uint16_t run_color = 0;
+
+ for (int dx = 0; dx < width; dx++) {
+ uint16_t pixel = sprite[dy * width + dx];
+
+ if (pixel != TRANSPARENT_COLOR) {
+ if (run_start == -1 || pixel != run_color) {
+ // Draw previous run if any
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
+ }
+ run_start = dx;
+ run_color = pixel;
+ }
+ } else {
+ // Draw previous run if any
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
+ run_start = -1;
+ }
+ }
+ }
+
+ // Draw final run if any
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, width - run_start, 1}, Color(run_color));
+ }
+ }
+}
+
+void DinoGameView::draw_bird_sprite(int x, int y, const uint16_t* sprite) {
+ for (int dy = 0; dy < BIRD_HEIGHT; dy++) {
+ int run_start = -1;
+ uint16_t run_color = 0;
+
+ for (int dx = 0; dx < BIRD_WIDTH; dx++) {
+ // Skip pixels that would be off-screen
+ if (x + dx < 0 || x + dx >= 320) {
+ // End any current run
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
+ run_start = -1;
+ }
+ continue;
+ }
+
+ uint16_t pixel = sprite[dy * BIRD_WIDTH + dx];
+
+ if (pixel != TRANSPARENT_COLOR) {
+ if (pixel == SPRITE_COLOR) {
+ pixel = Color::red().v;
+ }
+
+ if (run_start == -1 || pixel != run_color) {
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
+ }
+ run_start = dx;
+ run_color = pixel;
+ }
+ } else {
+ if (run_start != -1) {
+ painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
+ run_start = -1;
+ }
+ }
+ }
+
+ if (run_start != -1 && x + run_start < 320) {
+ int width = BIRD_WIDTH - run_start;
+ if (x + run_start + width > 320) {
+ width = 320 - (x + run_start);
+ }
+ if (width > 0) {
+ painter.fill_rectangle({x + run_start, y + dy, width, 1}, Color(run_color));
+ }
+ }
+ }
+}
+
+void DinoGameView::draw_dino_at(int x, int y, bool is_ducking, bool run_frame) {
+ if (is_ducking) {
+ if (run_frame) {
+ draw_dino_sprite(x, y, dino_ducking_leftstep);
+ } else {
+ draw_dino_sprite(x, y, dino_ducking_rightstep);
+ }
+ } else {
+ if (run_frame) {
+ draw_dino_sprite(x, y, dino_leftstep);
+ } else {
+ draw_dino_sprite(x, y, dino_default);
+ }
+ }
+}
+
+void DinoGameView::check_collision() {
+ int dino_left = DINO_X;
+ int dino_right = DINO_X + (ducking ? DINO_DUCK_WIDTH : DINO_WIDTH);
+ int dino_top = ducking ? DINO_DUCK_Y : (DINO_Y - jumpHeight);
+ int dino_bottom = dino_top + (ducking ? DINO_DUCK_HEIGHT : DINO_HEIGHT);
+
+ // Give a small hitbox reduction for fairness
+ dino_left += 5;
+ dino_right -= 5;
+ dino_top += 5;
+ dino_bottom -= 5;
+
+ // Check cactus collisions
+ for (int i = 0; i < MAX_OBSTACLES; i++) {
+ if (obstacles[i].active) {
+ int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
+ int obs_top = ground_y - obstacles[i].height;
+ int obs_bottom = ground_y;
+
+ if (dino_right > obstacles[i].x &&
+ dino_left < obstacles[i].x + obstacles[i].width &&
+ dino_bottom > obs_top &&
+ dino_top < obs_bottom) {
+ collided = true;
+ return;
+ }
+ }
+ }
+
+ // Check bird collision
+ if (bird_info.inGame) {
+ int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
+ int bird_y = base_y + bird_info.y_offset;
+
+ if (dino_right > bird_info.x_offset + 5 &&
+ dino_left < bird_info.x_offset + BIRD_WIDTH - 5 &&
+ dino_bottom > bird_y + 5 &&
+ dino_top < bird_y + BIRD_HEIGHT - 5) {
+ collided = true;
+ }
+ }
+}
+
+void DinoGameView::draw_current_score() {
+ auto style = *ui::Theme::getInstance()->fg_medium;
+
+ if (last_score != score) {
+ painter.fill_rectangle({10, 28, 60, 14}, Color::black());
+ painter.draw_string({10, 30}, style, score_to_string(score));
+ last_score = score;
+ }
+}
+
+void DinoGameView::draw_high_score() {
+ auto style = *ui::Theme::getInstance()->fg_light;
+ painter.fill_rectangle({152, 28, 100, 14}, Color::black());
+ painter.draw_string({152, 30}, style, "HI " + score_to_string(highScore));
+}
+
+void DinoGameView::jump() {
+ if (!jumping) {
+ jumping = true;
+ // If we're ducking when we jump, stand up
+ if (ducking) {
+ stand();
+ }
+ }
+}
+
+void DinoGameView::duck() {
+ if (!jumping) {
+ ducking = true;
+ duck_timer = 60; // 1 second at 60 FPS
+ }
+}
+
+void DinoGameView::stand() {
+ ducking = false;
+ duck_timer = 0;
+}
+
+void DinoGameView::step() {
+ ++steps;
+}
+
+bool DinoGameView::on_key(const KeyEvent key) {
+ if (key == KeyEvent::Select) {
+ if (game_state == GameState::MENU) {
+ game_state = GameState::PLAYING;
+ new_game();
+ draw_high_score();
+ } else if (game_state == GameState::PLAYING && !collided) {
+ jump();
+ } else if (game_state == GameState::GAME_OVER || collided) {
+ game_state = GameState::PLAYING; // Restart immediately
+ new_game();
+ draw_high_score();
+ }
+ } else if (key == KeyEvent::Down) {
+ if (game_state == GameState::PLAYING && !collided) {
+ duck();
+ }
+ } else if (key == KeyEvent::Up) {
+ if (game_state == GameState::PLAYING && !collided) {
+ stand();
+ }
+ }
+
+ set_dirty();
+ return true;
+}
+
+bool DinoGameView::on_encoder(const EncoderEvent delta) {
+ (void)delta;
+ return true;
+}
+
+} // namespace ui::external_app::dinogame
diff --git a/firmware/application/external/dinogame/ui_dinogame.hpp b/firmware/application/external/dinogame/ui_dinogame.hpp
new file mode 100644
index 000000000..a8d51e574
--- /dev/null
+++ b/firmware/application/external/dinogame/ui_dinogame.hpp
@@ -0,0 +1,254 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+/*
+ * Chrome Dino Game for Portapack Mayhem
+ * Based on the original DinoGame by various contributors
+ */
+
+#ifndef __UI_DINOGAME_H__
+#define __UI_DINOGAME_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "event_m0.hpp"
+#include "message.hpp"
+#include "irq_controls.hpp"
+#include "random.hpp"
+#include "lpc43xx_cpp.hpp"
+#include "ui_widget.hpp"
+#include "file.hpp"
+#include
+#include "app_settings.hpp"
+
+namespace ui::external_app::dinogame {
+
+// Color definitions
+enum {
+ White,
+ Blue,
+ Yellow,
+ Purple,
+ Green,
+ Red,
+ Maroon,
+ Orange,
+ Black,
+};
+
+// Game constants
+#define DINO_WIDTH 34
+#define DINO_HEIGHT 36
+#define DINO_DUCK_WIDTH 45
+#define DINO_DUCK_HEIGHT 22
+#define BIRD_WIDTH 34
+#define BIRD_HEIGHT 27
+#define GROUND_HEIGHT 10
+#define GAME_AREA_TOP 78
+#define GAME_AREA_HEIGHT 160
+#define DINO_X 30
+#define DINO_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_HEIGHT)
+#define DINO_DUCK_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_DUCK_HEIGHT)
+#define BIRD_Y_UP (GAME_AREA_TOP + 20)
+#define BIRD_Y_DOWN (GAME_AREA_TOP + 60)
+#define JUMP_MAX_HEIGHT 70
+#define JUMP_SPEED 3
+#define GAME_SPEED_BASE 3
+#define SPRITE_COLOR 0x528A
+#define TRANSPARENT_COLOR 0xFFFF
+#define MIN_OBSTACLE_DISTANCE 300
+#define MAX_OBSTACLE_DISTANCE 600
+
+// Game states
+enum class GameState {
+ MENU,
+ PLAYING,
+ GAME_OVER
+};
+
+// Walk styles
+enum class WalkStyle {
+ WALKING,
+ DUCKING
+};
+
+// Bird position
+enum class BirdPosition {
+ UP,
+ DOWN
+};
+
+// Structures
+struct Pterodactyl {
+ bool inGame = false;
+ BirdPosition y_position = BirdPosition::UP;
+ int16_t x_offset = 320;
+ int16_t y_offset = 0;
+ int8_t y_velocity = 0;
+ bool flop = false;
+};
+
+// Simple obstacle structure with proper initialization
+struct SimpleObstacle {
+ bool active;
+ int16_t x;
+ int16_t last_x;
+ uint8_t width;
+ uint8_t height;
+ uint8_t type; // 0=small, 1=large, 2=double, 3=triple
+
+ SimpleObstacle()
+ : active(false), x(0), last_x(0), width(0), height(0), type(0) {}
+};
+
+// External references to sprite data
+extern const uint16_t dino_default[];
+extern const uint16_t dino_leftstep[];
+extern const uint16_t dino_rightstep[];
+extern const uint16_t dino_ducking_leftstep[];
+extern const uint16_t dino_ducking_rightstep[];
+extern const uint16_t dino_gameover[];
+extern const uint16_t pterodactyl_upflop[];
+extern const uint16_t pterodactyl_downflop[];
+
+// Global painter
+extern Painter painter;
+
+// Function declarations
+void cls();
+void fillrect(int x1, int y1, int x2, int y2, int color);
+void rect(int x1, int y1, int x2, int y2, int color);
+void check_game_timer();
+void game_timer_check();
+
+// Ticker class
+using Callback = void (*)(void);
+
+class Ticker {
+ public:
+ Ticker() = default;
+ void attach(Callback func, double delay_sec);
+ void detach();
+};
+
+// Main view class
+class DinoGameView : public View {
+ public:
+ DinoGameView(NavigationView& nav);
+ void on_show() override;
+
+ std::string title() const override { return "Dino Game"; }
+
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_encoder(const EncoderEvent event) override;
+ bool on_key(KeyEvent key) override;
+
+ // Public for timer callback
+ GameState game_state = GameState::MENU;
+ void game_loop();
+ void show_menu();
+
+ private:
+ bool initialized = false;
+ NavigationView& nav_;
+
+ // Game variables
+ static constexpr uint8_t MAX_OBSTACLES = 1;
+ SimpleObstacle obstacles[MAX_OBSTACLES];
+ Pterodactyl bird_info;
+
+ int16_t jumpHeight = 0;
+ uint8_t ground_offset = 0;
+ uint8_t speed_modifier = 0;
+ bool runstate = false;
+ bool jumping = false;
+ bool falling = false;
+ bool collided = false;
+ bool ducking = false;
+ bool displayedGameOver = false;
+ bool last_ducking = false;
+ bool last_runstate = false;
+
+ uint32_t steps = 0;
+ uint32_t score = 0;
+ uint32_t highScore = 0;
+ uint32_t last_score = 999999; // Initialize to impossible value
+ int32_t next_obstacle_distance = 100;
+ int16_t last_obstacle_x = 320; // Track position of last spawned obstacle
+ int32_t obstacle_spawn_timer = 0;
+
+ // Position tracking for minimal redraw
+ int16_t last_dino_y = DINO_Y;
+ int16_t last_bird_x = -1;
+ int16_t last_bird_y = -1;
+ uint8_t duck_timer = 0;
+
+ // Menu animation
+ bool menu_initialized = false;
+ bool blink_state = true;
+ uint32_t blink_counter = 0;
+
+ // Current Score
+ bool score_drawn = false;
+
+ // Game timer
+ Ticker game_timer;
+
+ // Private methods
+ void init_game();
+ void new_game();
+ void update_obstacles();
+ void spawn_obstacle();
+ void draw_screen();
+ void draw_ground();
+ void draw_obstacle(const SimpleObstacle& obstacle);
+ void clear_obstacle_area(int x, int width, int height);
+ void draw_dino_sprite(int x, int y, const uint16_t* sprite);
+ void draw_bird_sprite(int x, int y, const uint16_t* sprite);
+ void draw_dino_at(int x, int y, bool is_ducking, bool run_frame);
+ void manage_bird();
+ void draw_score();
+ void draw_high_score();
+ void draw_current_score();
+ void show_game_over();
+ void jump();
+ void duck();
+ void stand();
+ void check_collision();
+ void step();
+ uint32_t get_steps() { return steps; }
+ std::string score_to_string(uint32_t score);
+
+ bool easy_mode = false;
+
+ Button button_difficulty{
+ {70, 195, 100, 20},
+ "Mode: HARD"};
+
+ app_settings::SettingsManager settings_{
+ "dinogame",
+ app_settings::Mode::NO_RF,
+ {{"highscore"sv, &highScore},
+ {"easy_mode"sv, &easy_mode}}};
+
+ Button dummy{
+ {screen_width, 0, 0, 0},
+ ""};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+
+} // namespace ui::external_app::dinogame
+
+#endif /* __UI_DINOGAME_H__ */
diff --git a/firmware/application/external/doom/main.cpp b/firmware/application/external/doom/main.cpp
new file mode 100644
index 000000000..417b7c625
--- /dev/null
+++ b/firmware/application/external/doom/main.cpp
@@ -0,0 +1,67 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_doom.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::doom {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::doom
+
+extern "C" {
+
+__attribute__((section(".external_app.app_doom.application_information"), used)) application_information_t _application_information_doom = {
+ (uint8_t*)0x00000000,
+ ui::external_app::doom::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+ "Doom",
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x77,
+ 0xDF,
+ 0xFF,
+ 0xDF,
+ 0xD9,
+ 0xFD,
+ 0x89,
+ 0xF8,
+ 0x89,
+ 0xE8,
+ 0x89,
+ 0xA8,
+ 0x89,
+ 0xA8,
+ 0xD9,
+ 0xAD,
+ 0x79,
+ 0xAF,
+ 0x2D,
+ 0xAA,
+ 0x07,
+ 0xA8,
+ 0x03,
+ 0xA0,
+ 0x01,
+ 0x80,
+ 0x00,
+ 0x00,
+ },
+ ui::Color::red().v,
+ app_location_t::GAMES,
+ -1,
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+}
diff --git a/firmware/application/external/doom/ui_doom.cpp b/firmware/application/external/doom/ui_doom.cpp
new file mode 100644
index 000000000..dc63b60a8
--- /dev/null
+++ b/firmware/application/external/doom/ui_doom.cpp
@@ -0,0 +1,1141 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_doom.hpp"
+#include "ui.hpp"
+
+namespace ui::external_app::doom {
+
+//clang-format off
+int SCREEN_WIDTH = 0;
+int SCREEN_HEIGHT = 0;
+int RENDER_HEIGHT = 0;
+int HALF_WIDTH = 0;
+int HALF_HEIGHT = 0;
+#define LEVEL_WIDTH_BASE 6
+#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE)
+#define LEVEL_HEIGHT 57
+#define LEVEL_SIZE (LEVEL_WIDTH / 2 * LEVEL_HEIGHT)
+#define MAX_RENDER_DEPTH 12
+#define MIN_ENTITIES 15
+#define MAX_ENTITIES 25
+#define RES_DIVIDER 2
+#define DISTANCE_MULTIPLIER 20
+#define ROT_SPEED 0.25
+#define MOV_SPEED 1.0
+#define JOGGING_SPEED 1.2
+#define MAX_ENTITY_DISTANCE 200
+#define ITEM_COLLIDER_DIST 6
+#define PI 3.14159265358979323846
+#define GUN_WIDTH 30
+#define GUN_HEIGHT 40
+#define GUN_TARGET_POS 24
+#define GUN_SHOT_POS (GUN_TARGET_POS + 6)
+#define ENEMY_SIZE 16
+#define ENEMY_SPEED 0.03
+#define ENEMY_MELEE_DIST 20
+#define ENEMY_MELEE_DAMAGE 2
+#define GUN_MAX_DAMAGE 15
+
+#define ACTIVATION_RADIUS 120
+#define STATE_INACTIVE 6
+
+#define COLOR_BACKGROUND Black
+#define COLOR_WALL_LIGHT Green
+#define COLOR_WALL_DARK Maroon
+#define COLOR_FLOOR_DARK Black
+//clang-format on
+
+// Stole this level map from Flipper Zero Doom, but he stole it from a guy who stole it and that guy probably stole it too. Argh!
+static const uint8_t level[LEVEL_SIZE] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x90, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF2, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x4F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0x05, 0x00, 0x00, 0x90, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0xFF,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF2, 0x02, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x40, 0x80, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x40, 0x00, 0x02, 0x00, 0x90, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+struct Coords {
+ double x, y;
+};
+
+struct Player {
+ Coords pos;
+ Coords dir;
+ Coords plane;
+ double velocity;
+ uint8_t health;
+ uint8_t keys;
+ uint8_t ammo;
+};
+
+struct Entity {
+ uint16_t uid;
+ Coords pos;
+ Coords death_pos;
+ uint8_t state;
+ uint8_t health;
+ uint8_t distance;
+ uint8_t timer;
+};
+
+static Ticker game_timer;
+static Player player;
+static Entity entities[MAX_ENTITIES];
+static uint8_t num_entities = 0;
+static uint16_t kills = 0;
+static uint8_t scene = 0;
+static bool up, down, left, right, fired;
+static double jogging, view_height;
+static bool needs_redraw = false;
+static bool needs_gun_redraw = false;
+static uint8_t gun_pos = GUN_TARGET_POS;
+static bool gun_fired = false;
+static int prev_gun_x = 0;
+static int prev_gun_y = 0;
+
+Coords create_coords(double x, double y) {
+ Coords c;
+ c.x = x;
+ c.y = y;
+ return c;
+}
+
+Player create_player(double x, double y) {
+ Player p;
+ p.pos = create_coords(x + 0.5, y + 0.5);
+ p.dir = create_coords(1, 0);
+ p.plane = create_coords(0, -0.66);
+ p.velocity = 0;
+ p.health = 100;
+ p.keys = 0;
+ p.ammo = 99;
+ return p;
+}
+
+Entity create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t state, uint8_t health) {
+ Entity e;
+ e.uid = ((y << 6) | x) << 4 | type;
+ e.pos = create_coords(x + 0.5, y + 0.5);
+ e.death_pos = create_coords(x + 0.5, y + 0.5);
+ e.state = state;
+ e.health = health;
+ e.distance = 0;
+ e.timer = 0;
+ return e;
+}
+
+uint8_t get_block_at(uint8_t x, uint8_t y) {
+ if (x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) return 0xF;
+ return level[((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2] >> (!(x % 2) * 4) & 0b1111;
+}
+
+Coords translate_into_view(Coords pos) {
+ double sprite_x = pos.x - player.pos.x;
+ double sprite_y = pos.y - player.pos.y;
+ double inv_det = 1.0 / (player.plane.x * player.dir.y - player.dir.x * player.plane.y);
+ double transform_x = inv_det * (player.dir.y * sprite_x - player.dir.x * sprite_y);
+ double transform_y = inv_det * (-player.plane.y * sprite_x + player.plane.x * sprite_y);
+ return create_coords(transform_x, transform_y);
+}
+
+void spawn_entity(uint8_t type, uint8_t x, uint8_t y) {
+ if (num_entities >= MAX_ENTITIES) return;
+ entities[num_entities] = create_entity(type, x, y, STATE_INACTIVE, 50);
+ num_entities++;
+}
+
+void spawn_random_entity(uint8_t type) {
+ if (num_entities >= MAX_ENTITIES) return;
+ std::srand(LPC_RTC->CTIME0);
+ uint8_t spawn_x, spawn_y;
+ do {
+ spawn_x = std::rand() % LEVEL_WIDTH;
+ spawn_y = std::rand() % LEVEL_HEIGHT;
+ } while (get_block_at(spawn_x, spawn_y) == 0xF ||
+ (spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y));
+
+ entities[num_entities] = create_entity(type, spawn_x, spawn_y, STATE_INACTIVE, 50);
+ num_entities++;
+}
+
+void remove_entity(uint8_t index) {
+ for (uint8_t i = index; i < num_entities - 1; i++) {
+ entities[i] = entities[i + 1];
+ }
+ num_entities--;
+
+ if (num_entities < MIN_ENTITIES) {
+ uint8_t attempts = 0;
+ bool spawned = false;
+
+ while (!spawned && attempts < 50) {
+ std::srand(LPC_RTC->CTIME0 + attempts);
+ uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
+ uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
+
+ int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
+ int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
+ uint16_t dist_squared = dx * dx + dy * dy;
+
+ if (dist_squared >= 64 &&
+ get_block_at(spawn_x, spawn_y) != 0xF &&
+ !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
+ entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
+ entities[num_entities].pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].pos.y = (double)spawn_y + 0.5;
+ entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
+
+ if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) != 0xF) {
+ num_entities++;
+ spawned = true;
+ }
+ }
+ attempts++;
+ }
+ }
+}
+
+void initialize_level() {
+ for (uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) {
+ for (uint8_t x = 0; x < LEVEL_WIDTH; x++) {
+ uint8_t block = get_block_at(x, y);
+ if (block == 0x1) {
+ player = create_player(x, y);
+ break;
+ }
+ }
+ }
+ num_entities = 0;
+
+ uint8_t initial_enemies = 0;
+ uint8_t attempts = 0;
+
+ while (initial_enemies < 2 && num_entities < MAX_ENTITIES && attempts < 50) {
+ std::srand(LPC_RTC->CTIME0 + attempts);
+ int8_t offset_x = (std::rand() % 11) - 5;
+ int8_t offset_y = (std::rand() % 11) - 5;
+
+ if (abs(offset_x) < 3 && abs(offset_y) < 3) {
+ attempts++;
+ continue;
+ }
+
+ int16_t temp_x = (int16_t)player.pos.x + offset_x;
+ int16_t temp_y = (int16_t)player.pos.y + offset_y;
+ uint8_t spawn_x = (temp_x < 0) ? 0 : (temp_x >= LEVEL_WIDTH) ? LEVEL_WIDTH - 1
+ : temp_x;
+ uint8_t spawn_y = (temp_y < 0) ? 0 : (temp_y >= LEVEL_HEIGHT) ? LEVEL_HEIGHT - 1
+ : temp_y;
+
+ if (get_block_at(spawn_x, spawn_y) != 0xF &&
+ !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
+ entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
+ entities[num_entities].pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].pos.y = (double)spawn_y + 0.5;
+ entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
+
+ if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) {
+ attempts++;
+ continue;
+ }
+
+ num_entities++;
+ initial_enemies++;
+ }
+ attempts++;
+ }
+
+ attempts = 0;
+ while (num_entities < MIN_ENTITIES && attempts < 100) {
+ std::srand(LPC_RTC->CTIME0 + attempts + 100);
+
+ uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
+ uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
+
+ int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
+ int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
+ uint16_t dist_squared = dx * dx + dy * dy;
+
+ if (dist_squared >= 25 &&
+ get_block_at(spawn_x, spawn_y) != 0xF &&
+ !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
+ entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
+ entities[num_entities].pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].pos.y = (double)spawn_y + 0.5;
+ entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
+ entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
+
+ if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) {
+ attempts++;
+ continue;
+ }
+
+ num_entities++;
+ }
+ attempts++;
+ }
+}
+
+void fire() {
+ if (player.ammo > 0) {
+ uint8_t closest_i = 255;
+ double min_dist = 1000.0;
+ for (uint8_t i = 0; i < num_entities; i++) {
+ if (entities[i].state == 5) continue;
+ double dx = entities[i].pos.x - player.pos.x;
+ double dy = entities[i].pos.y - player.pos.y;
+ double dist = sqrt(dx * dx + dy * dy) * DISTANCE_MULTIPLIER;
+ double dot = dx * player.dir.x + dy * player.dir.y;
+ double angle = acos(dot / (dist / DISTANCE_MULTIPLIER));
+ if (dist < 100 && angle < 0.2 && dot > 0 && dist < min_dist) {
+ closest_i = i;
+ min_dist = dist;
+ }
+ }
+ if (closest_i != 255) {
+ uint8_t damage = fmin(GUN_MAX_DAMAGE, GUN_MAX_DAMAGE * (100 - min_dist) / 100);
+ if (damage > 0) {
+ entities[closest_i].health = fmax(0, entities[closest_i].health - damage);
+ entities[closest_i].state = 4;
+ entities[closest_i].timer = 4;
+ needs_redraw = true;
+ }
+ }
+ player.ammo--;
+ }
+}
+
+void update_entities() {
+ uint8_t i = 0;
+ while (i < num_entities) {
+ entities[i].distance = sqrt(pow(player.pos.x - entities[i].pos.x, 2) + pow(player.pos.y - entities[i].pos.y, 2)) * DISTANCE_MULTIPLIER;
+
+ if (entities[i].state != 5) {
+ if (entities[i].distance > ACTIVATION_RADIUS && entities[i].state != STATE_INACTIVE) {
+ entities[i].state = STATE_INACTIVE;
+ needs_redraw = true;
+ } else if (entities[i].distance <= ACTIVATION_RADIUS && entities[i].state == STATE_INACTIVE) {
+ entities[i].state = 0;
+ needs_redraw = true;
+ }
+ }
+
+ if (entities[i].timer > 0) entities[i].timer--;
+
+ if (entities[i].health == 0 && entities[i].state == 5 && entities[i].timer == 0) {
+ kills++;
+ remove_entity(i);
+ continue;
+ }
+ if (entities[i].health == 0 && entities[i].state != 5) {
+ entities[i].state = 5;
+ entities[i].timer = 6;
+ entities[i].death_pos = entities[i].pos;
+ needs_redraw = true;
+ }
+ if (entities[i].state == 4 && entities[i].timer == 0) {
+ entities[i].state = 0;
+ needs_redraw = true;
+ }
+
+ if (entities[i].state != STATE_INACTIVE && entities[i].state != 4 && entities[i].state != 5) {
+ double dx = player.pos.x - entities[i].pos.x;
+ double dy = player.pos.y - entities[i].pos.y;
+ double dist = sqrt(dx * dx + dy * dy);
+
+ if (dist <= (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER) && entities[i].timer == 0) {
+ entities[i].state = 3;
+ player.health = fmax(0, player.health - ENEMY_MELEE_DAMAGE);
+ entities[i].timer = 30;
+ needs_redraw = true;
+ }
+
+ double move_dist = fmin(ENEMY_SPEED, dist - 0.2);
+
+ bool moved = false;
+
+ double new_x = entities[i].pos.x + (dx / dist) * move_dist;
+ double new_y = entities[i].pos.y + (dy / dist) * move_dist;
+ if (get_block_at((uint8_t)new_x, (uint8_t)entities[i].pos.y) != 0xF) {
+ entities[i].pos.x = new_x;
+ moved = true;
+ }
+ if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)new_y) != 0xF) {
+ entities[i].pos.y = new_y;
+ moved = true;
+ }
+
+ if (!moved) {
+ bool wall_x_pos = get_block_at((uint8_t)entities[i].pos.x + 1, (uint8_t)entities[i].pos.y) == 0xF;
+ bool wall_x_neg = get_block_at((uint8_t)entities[i].pos.x - 1, (uint8_t)entities[i].pos.y) == 0xF;
+ bool wall_y_pos = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y + 1) == 0xF;
+ bool wall_y_neg = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y - 1) == 0xF;
+
+ uint8_t dir_choice = (entities[i].uid + LPC_RTC->CTIME0) % 4;
+
+ if (wall_x_pos || wall_x_neg) {
+ double try_y = entities[i].pos.y + (dir_choice < 2 ? 1 : -1) * move_dist;
+ if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) {
+ entities[i].pos.y = try_y;
+ moved = true;
+ }
+ } else if (wall_y_pos || wall_y_neg) {
+ double try_x = entities[i].pos.x + (dir_choice < 2 ? 1 : -1) * move_dist;
+ if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) {
+ entities[i].pos.x = try_x;
+ moved = true;
+ }
+ }
+
+ if (!moved) {
+ double angle = (entities[i].uid * 17 + LPC_RTC->CTIME0) % 628 / 100.0;
+ double try_x = entities[i].pos.x + cos(angle) * move_dist;
+ double try_y = entities[i].pos.y + sin(angle) * move_dist;
+
+ if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) {
+ entities[i].pos.x = try_x;
+ moved = true;
+ }
+ if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) {
+ entities[i].pos.y = try_y;
+ moved = true;
+ }
+ }
+ }
+
+ for (int dx = -1; dx <= 1; dx++) {
+ for (int dy = -1; dy <= 1; dy++) {
+ if (dx == 0 && dy == 0) continue;
+ if (get_block_at((uint8_t)(entities[i].pos.x + dx * 0.3), (uint8_t)(entities[i].pos.y + dy * 0.3)) == 0xF) {
+ entities[i].pos.x -= dx * 0.05;
+ entities[i].pos.y -= dy * 0.05;
+ }
+ }
+ }
+
+ if (dist > (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER)) entities[i].state = 0;
+ }
+ i++;
+ }
+}
+
+void update_game() {
+ bool state_changed = false;
+ bool gun_only_change = false;
+
+ if (scene == 1) {
+ if (player.health > 0) {
+ if (up) {
+ player.velocity += (MOV_SPEED - player.velocity) * 0.4;
+ jogging = fabs(player.velocity) * 5;
+ up = false;
+ state_changed = true;
+ } else if (down) {
+ player.velocity += (-MOV_SPEED - player.velocity) * 0.4;
+ jogging = fabs(player.velocity) * 5;
+ down = false;
+ state_changed = true;
+ } else {
+ double old_velocity = player.velocity;
+ player.velocity *= 0.5;
+ jogging = fabs(player.velocity) * 5;
+ if (fabs(player.velocity) < 0.001) player.velocity = 0;
+ if (old_velocity != player.velocity) state_changed = true;
+ }
+
+ if (right) {
+ double old_dir_x = player.dir.x;
+ player.dir.x = player.dir.x * cos(-ROT_SPEED) - player.dir.y * sin(-ROT_SPEED);
+ player.dir.y = old_dir_x * sin(-ROT_SPEED) + player.dir.y * cos(-ROT_SPEED);
+ double old_plane_x = player.plane.x;
+ player.plane.x = player.plane.x * cos(-ROT_SPEED) - player.plane.y * sin(-ROT_SPEED);
+ player.plane.y = old_plane_x * sin(-ROT_SPEED) + player.plane.y * cos(-ROT_SPEED);
+ right = false;
+ state_changed = true;
+ } else if (left) {
+ double old_dir_x = player.dir.x;
+ player.dir.x = player.dir.x * cos(ROT_SPEED) - player.dir.y * sin(ROT_SPEED);
+ player.dir.y = old_dir_x * sin(ROT_SPEED) + player.dir.y * cos(ROT_SPEED);
+ double old_plane_x = player.plane.x;
+ player.plane.x = player.plane.x * cos(ROT_SPEED) - player.plane.y * sin(ROT_SPEED);
+ player.plane.y = old_plane_x * sin(ROT_SPEED) + player.plane.y * cos(ROT_SPEED);
+ left = false;
+ state_changed = true;
+ }
+
+ if (player.velocity != 0) {
+ view_height = fabs(sin(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 6 * jogging;
+ } else {
+ view_height = 0;
+ }
+
+ if (fabs(player.velocity) > 0.003) {
+ double new_x = player.pos.x + player.dir.x * player.velocity;
+ double new_y = player.pos.y + player.dir.y * player.velocity;
+ bool can_move_x = get_block_at((uint8_t)new_x, (uint8_t)player.pos.y) != 0xF;
+ bool can_move_y = get_block_at((uint8_t)player.pos.x, (uint8_t)new_y) != 0xF;
+ for (uint8_t i = 0; i < num_entities; i++) {
+ if (entities[i].state != 5 && entities[i].state != STATE_INACTIVE) {
+ double dx = new_x - entities[i].pos.x;
+ double dy = player.pos.y - entities[i].pos.y;
+ if (sqrt(dx * dx + dy * dy) < 0.5) {
+ can_move_x = false;
+ }
+ dx = player.pos.x - entities[i].pos.x;
+ dy = new_y - entities[i].pos.y;
+ if (sqrt(dx * dx + dy * dy) < 0.5) {
+ can_move_y = false;
+ }
+ }
+ }
+ if (can_move_x) {
+ player.pos.x = new_x;
+ state_changed = true;
+ }
+ if (can_move_y) {
+ player.pos.y = new_y;
+ state_changed = true;
+ }
+ } else if (player.velocity != 0) {
+ player.velocity = 0;
+ state_changed = true;
+ }
+
+ if (gun_pos > GUN_TARGET_POS) {
+ gun_pos -= 1;
+ gun_only_change = true;
+ } else if (gun_pos < GUN_TARGET_POS) {
+ gun_pos += 2;
+ gun_only_change = true;
+ } else if (fired) {
+ gun_pos = GUN_SHOT_POS;
+ fire();
+ needs_gun_redraw = true;
+ gun_only_change = true;
+ fired = false;
+ }
+ } else {
+ if (view_height > -10) {
+ view_height--;
+ state_changed = true;
+ }
+ if (gun_pos > 1) {
+ gun_pos -= 2;
+ gun_only_change = true;
+ }
+ }
+ update_entities();
+ }
+
+ if (state_changed && !gun_only_change)
+ needs_redraw = true;
+ else if (gun_only_change)
+ needs_gun_redraw = true;
+}
+
+static void game_timer_check() {
+ if (scene == 1) update_game();
+}
+
+void render_gun(Painter& painter, uint8_t gun_pos, double jogging) {
+ int x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging;
+ int y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging;
+
+ prev_gun_x = x;
+ prev_gun_y = y;
+
+ int recoil = gun_pos > GUN_TARGET_POS ? (gun_pos - GUN_TARGET_POS) / 2 : 0;
+
+ // Gun body - shotgun style with more detail
+ painter.fill_rectangle({x + 8, y + 8, 18, 12}, pp_colors[Green]);
+ painter.fill_rectangle({x + 6, y + 10, 2, 8}, pp_colors[Black]);
+ painter.fill_rectangle({x + 26, y + 10, 2, 8}, pp_colors[Black]);
+
+ // Barrel
+ painter.fill_rectangle({x + 12, y - 4, 8, 12}, pp_colors[Green]);
+ painter.fill_rectangle({x + 14, y - 6, 4, 2}, pp_colors[Green]);
+
+ // Barrel shading
+ painter.fill_rectangle({x + 12, y - 4, 2, 12}, pp_colors[White]);
+ painter.fill_rectangle({x + 18, y - 4, 2, 12}, pp_colors[Black]);
+
+ // Handle
+ painter.fill_rectangle({x + 12, y + 20, 8, 20}, pp_colors[Maroon]);
+ painter.fill_rectangle({x + 10, y + 20, 2, 18}, pp_colors[White]);
+ painter.fill_rectangle({x + 20, y + 20, 2, 18}, pp_colors[Black]);
+
+ // Trigger guard
+ painter.fill_rectangle({x + 10, y + 20, 12, 2}, pp_colors[Green]);
+ painter.fill_rectangle({x + 10, y + 20, 2, 6}, pp_colors[Green]);
+ painter.fill_rectangle({x + 20, y + 20, 2, 6}, pp_colors[Green]);
+ painter.fill_rectangle({x + 10, y + 26, 12, 2}, pp_colors[Green]);
+
+ // Trigger
+ painter.fill_rectangle({x + 14, y + 22 + recoil / 2, 4, 4}, pp_colors[Black]);
+
+ // Pump action
+ painter.fill_rectangle({x + 8, y + 4, 16, 4}, pp_colors[Maroon]);
+ painter.fill_rectangle({x + 7, y + 4, 1, 4}, pp_colors[White]);
+ painter.fill_rectangle({x + 24, y + 4, 1, 4}, pp_colors[Black]);
+
+ // Action slide rails
+ painter.fill_rectangle({x + 10, y + 8, 1, 8 + recoil}, pp_colors[White]);
+ painter.fill_rectangle({x + 21, y + 8, 1, 8 + recoil}, pp_colors[Black]);
+
+ // Ammo chamber
+ painter.fill_rectangle({x + 24, y + 12, 4, 4}, pp_colors[Black]);
+
+ // Muzzle flash
+ if (gun_pos > GUN_TARGET_POS) {
+ int flash_size = 10 + (gun_pos - GUN_TARGET_POS) * 2;
+ int flash_center_x = x + 16;
+ int flash_center_y = y - 6;
+
+ // Core of flash
+ painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y - flash_size / 2,
+ flash_size / 2, flash_size},
+ pp_colors[White]);
+
+ // Outer yellow glow
+ painter.fill_rectangle({flash_center_x - flash_size / 2, flash_center_y - flash_size / 4,
+ flash_size, flash_size / 2},
+ pp_colors[Yellow]);
+
+ // Left spike
+ painter.fill_rectangle({flash_center_x - flash_size, flash_center_y - 2,
+ flash_size / 2, 4},
+ pp_colors[Yellow]);
+
+ // Right spike
+ painter.fill_rectangle({flash_center_x + flash_size / 2, flash_center_y - 2,
+ flash_size / 2, 4},
+ pp_colors[Yellow]);
+
+ // Top spike
+ painter.fill_rectangle({flash_center_x - 2, flash_center_y - flash_size,
+ 4, flash_size / 2},
+ pp_colors[Yellow]);
+
+ // Small random sparks
+ if (LPC_RTC->CTIME0 % 2) {
+ painter.fill_rectangle({flash_center_x - flash_size / 2 - 2, flash_center_y - flash_size / 2 - 2,
+ 2, 2},
+ pp_colors[Yellow]);
+ painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 3,
+ 2, 2},
+ pp_colors[Yellow]);
+ } else {
+ painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 2,
+ 2, 2},
+ pp_colors[Yellow]);
+ painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y + flash_size / 4,
+ 2, 2},
+ pp_colors[Yellow]);
+ }
+
+ // Smoke effect
+ if (gun_pos > GUN_TARGET_POS + 2) {
+ int smoke_y = flash_center_y - flash_size - 4;
+ painter.fill_rectangle({flash_center_x - 6, smoke_y, 12, 4}, pp_colors[White]);
+ painter.fill_rectangle({flash_center_x - 8, smoke_y - 4, 16, 4}, pp_colors[White]);
+ painter.fill_rectangle({flash_center_x - 6, smoke_y - 8, 12, 4}, pp_colors[White]);
+ }
+ }
+
+ // Shell ejection
+ if (gun_pos > GUN_TARGET_POS && gun_pos < GUN_TARGET_POS + 6) {
+ int shell_x = x + 24 + (gun_pos - GUN_TARGET_POS) * 2;
+ int shell_y = y + 10 - (gun_pos - GUN_TARGET_POS);
+
+ painter.fill_rectangle({shell_x, shell_y, 4, 2}, pp_colors[Yellow]);
+ painter.fill_rectangle({shell_x + 1, shell_y - 1, 2, 4}, pp_colors[Yellow]);
+ }
+}
+
+void render_entities(Painter& painter) {
+ for (uint8_t i = 0; i < num_entities; i++) {
+ if (entities[i].state == STATE_INACTIVE) continue;
+ Coords transform = translate_into_view(entities[i].state == 5 ? entities[i].death_pos : entities[i].pos);
+ if (transform.y <= 0.1 || transform.y > MAX_RENDER_DEPTH) continue;
+
+ int16_t sprite_x = HALF_WIDTH * (1.0 + transform.x / transform.y);
+ int16_t sprite_y = HALF_HEIGHT + view_height / transform.y;
+ uint8_t base_size = ENEMY_SIZE / fmax(0.1, transform.y);
+
+ if (sprite_x < -base_size || sprite_x > SCREEN_WIDTH + base_size || sprite_y < 0 || sprite_y > RENDER_HEIGHT) continue;
+
+ uint8_t size = base_size * 2;
+ int16_t half_size = size / 2;
+ int16_t x_start = sprite_x - half_size;
+ int16_t y_start = sprite_y - half_size;
+
+ Color body_color = Color(180, 40, 20);
+ Color spine_color = Color(120, 20, 10);
+ Color eye_color = Color(255, 165, 0);
+ Color pupil_color = Color(255, 0, 0);
+ Color claw_color = Color(60, 60, 60);
+ Color teeth_color = Color(220, 220, 220);
+ Color blood_color = Color(150, 0, 0);
+ Color horn_color = Color(90, 30, 10);
+
+ if (entities[i].state == 5) {
+ // Death animation - gibbed/flattened demon
+ uint8_t death_phase = entities[i].timer / 5;
+ if (death_phase > 3) death_phase = 3;
+
+ if (death_phase == 0) {
+ // Initial death - falling apart
+ painter.fill_rectangle({x_start + size / 5, sprite_y, size * 3 / 5, size / 3}, body_color);
+ painter.fill_rectangle({x_start + size * 2 / 5, sprite_y - size / 6, size / 5, size / 4}, horn_color);
+ painter.fill_rectangle({x_start + size / 3, sprite_y + size / 8, size / 8, size / 6}, blood_color);
+ painter.fill_rectangle({x_start + size / 2, sprite_y + size / 6, size / 6, size / 8}, blood_color);
+ painter.fill_rectangle({x_start + size / 4, sprite_y + size / 4, size / 5, size / 10}, claw_color);
+ painter.fill_rectangle({x_start + size * 3 / 5, sprite_y + size / 5, size / 5, size / 10}, claw_color);
+ } else if (death_phase == 1) {
+ // More splattered
+ painter.fill_rectangle({x_start + size / 6, sprite_y, size * 2 / 3, size / 4}, body_color);
+ painter.fill_rectangle({x_start + size / 4, sprite_y + size / 8, size / 6, size / 6}, blood_color);
+ painter.fill_rectangle({x_start + size / 2, sprite_y + size / 10, size / 6, size / 8}, blood_color);
+ painter.fill_rectangle({x_start + size * 2 / 3, sprite_y + size / 12, size / 8, size / 8}, blood_color);
+ } else {
+ // Final gore puddle
+ painter.fill_rectangle({x_start + size / 8, sprite_y, size * 3 / 4, size / 5}, body_color);
+ painter.fill_rectangle({x_start + size / 6, sprite_y - size / 20, size * 2 / 3, size / 10}, blood_color);
+ painter.fill_rectangle({x_start + size / 4, sprite_y + size / 10, size / 5, size / 12}, blood_color);
+ painter.fill_rectangle({x_start + size / 2, sprite_y + size / 12, size / 4, size / 10}, blood_color);
+ }
+ } else {
+ // Body
+ painter.fill_rectangle({x_start + size / 3, y_start + size / 4, size / 3, size * 3 / 4}, body_color);
+
+ // Spine/backbone
+ painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 6, size / 6, size / 2}, spine_color);
+
+ // Head
+ painter.fill_rectangle({x_start + size * 3 / 8, y_start, size * 5 / 16, size / 3}, body_color);
+
+ // Horns
+ painter.fill_rectangle({x_start + size / 4, y_start - size / 6, size / 8, size / 5}, horn_color);
+ painter.fill_rectangle({x_start + size * 5 / 8, y_start - size / 6, size / 8, size / 5}, horn_color);
+
+ // Eyes
+ if (entities[i].state != 4) {
+ uint8_t eye_anim = (LPC_RTC->CTIME0 % 8 == 0) ? 1 : 0;
+
+ painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color);
+ painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color);
+
+ painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color);
+ painter.fill_rectangle({x_start + size * 17 / 32, y_start + size / 8, size / 20, size / 20}, pupil_color);
+ } else {
+ // Damaged eye
+ painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6}, eye_color);
+ painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color);
+
+ // Blood from damaged eye
+ painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 6, size / 10, size / 8}, blood_color);
+ painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 12, size / 12}, blood_color);
+ }
+
+ // Teeth/mouth
+ painter.fill_rectangle({x_start + size * 7 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
+ painter.fill_rectangle({x_start + size / 2, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
+ painter.fill_rectangle({x_start + size * 9 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
+
+ // Attacking state - claws extended
+ if (entities[i].state == 3) {
+ uint8_t claw_anim = (LPC_RTC->CTIME0 % 4) / 2;
+
+ if (claw_anim == 0) {
+ // Claws forward
+ painter.fill_rectangle({x_start - size / 8, y_start + size * 5 / 12, size / 3, size / 6}, claw_color);
+ painter.fill_rectangle({x_start - size / 12, y_start + size / 2, size / 5, size / 4}, claw_color);
+ painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 5 / 12, size / 3, size / 6}, claw_color);
+ painter.fill_rectangle({x_start + size * 3 / 4, y_start + size / 2, size / 5, size / 4}, claw_color);
+ } else {
+ // Claws slashing
+ painter.fill_rectangle({x_start, y_start + size * 4 / 12, size / 4, size / 5}, claw_color);
+ painter.fill_rectangle({x_start + size / 12, y_start + size * 6 / 12, size / 5, size / 3}, claw_color);
+ painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 4 / 12, size / 4, size / 5}, claw_color);
+ painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 6 / 12, size / 5, size / 3}, claw_color);
+ }
+ } else {
+ // Normal claws
+ painter.fill_rectangle({x_start + size / 6, y_start + size / 2, size / 5, size / 5}, claw_color);
+ painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 5, size / 5}, claw_color);
+ }
+
+ // Legs animation
+ uint8_t leg_anim = (LPC_RTC->CTIME0 % 4) / 2;
+ if (entities[i].state == 0 && leg_anim == 0) {
+ // Walking animation frame 1
+ painter.fill_rectangle({x_start + size / 4, y_start + size * 5 / 6, size / 6, size / 5}, body_color);
+ painter.fill_rectangle({x_start + size / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color);
+ painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 3 / 4, size / 6, size / 4}, body_color);
+ painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color);
+ } else {
+ // Walking animation frame 2
+ painter.fill_rectangle({x_start + size / 4, y_start + size * 3 / 4, size / 6, size / 4}, body_color);
+ painter.fill_rectangle({x_start + size / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color);
+ painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 5 / 6, size / 6, size / 5}, body_color);
+ painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color);
+ }
+
+ // Injury effects for damaged state
+ if (entities[i].state == 4) {
+ painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 10, size / 8}, blood_color);
+ painter.fill_rectangle({x_start + size * 5 / 12, y_start + size * 7 / 12, size / 8, size / 8}, blood_color);
+ painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 10, size / 10}, blood_color);
+ }
+ }
+ }
+}
+
+void render_map(Painter& painter, bool full_clear, int16_t x_start = 0, int16_t x_end = SCREEN_WIDTH) {
+ if (full_clear) {
+ painter.fill_rectangle({0, 0, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(64, 64, 128));
+ painter.fill_rectangle({0, RENDER_HEIGHT / 2, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(32, 32, 32));
+ }
+
+ for (uint8_t x = x_start; x < x_end; x += RES_DIVIDER) {
+ double camera_x = 2 * (double)x / SCREEN_WIDTH - 1;
+ double ray_x = player.dir.x + player.plane.x * camera_x;
+ double ray_y = player.dir.y + player.plane.y * camera_x;
+
+ if (fabs(ray_x) < 0.00001) ray_x = 0.00001;
+ if (fabs(ray_y) < 0.00001) ray_y = 0.00001;
+
+ uint8_t map_x = (uint8_t)player.pos.x;
+ uint8_t map_y = (uint8_t)player.pos.y;
+ double delta_x = fabs(1 / ray_x);
+ double delta_y = fabs(1 / ray_y);
+
+ int8_t step_x, step_y;
+ double side_x, side_y;
+
+ if (ray_x < 0) {
+ step_x = -1;
+ side_x = (player.pos.x - map_x) * delta_x;
+ } else {
+ step_x = 1;
+ side_x = (map_x + 1.0 - player.pos.x) * delta_x;
+ }
+ if (ray_y < 0) {
+ step_y = -1;
+ side_y = (player.pos.y - map_y) * delta_y;
+ } else {
+ step_y = 1;
+ side_y = (map_y + 1.0 - player.pos.y) * delta_y;
+ }
+
+ uint8_t depth = 0;
+ bool hit = false;
+ bool side = false;
+ double perpWallDist = 0.0;
+
+ bool close_wall_handled = false;
+ uint8_t current_block = get_block_at(map_x, map_y);
+ if (current_block == 0xF) {
+ hit = true;
+ perpWallDist = 0.1;
+ close_wall_handled = true;
+ } else {
+ uint8_t check_blocks[4][3] = {
+ {(uint8_t)(map_x + 1), map_y, 0},
+ {(uint8_t)(map_x - 1), map_y, 0},
+ {map_x, (uint8_t)(map_y + 1), 1},
+ {map_x, (uint8_t)(map_y - 1), 1}};
+
+ for (int i = 0; i < 4; i++) {
+ if (get_block_at(check_blocks[i][0], check_blocks[i][1]) == 0xF) {
+ double dist_to_wall;
+ if (check_blocks[i][2] == 0) {
+ dist_to_wall = (check_blocks[i][0] > map_x) ? check_blocks[i][0] - player.pos.x : player.pos.x - check_blocks[i][0];
+ } else {
+ dist_to_wall = (check_blocks[i][1] > map_y) ? check_blocks[i][1] - player.pos.y : player.pos.y - check_blocks[i][1];
+ }
+
+ if (dist_to_wall < 0.2) {
+ double dot_product;
+ if (check_blocks[i][2] == 0) {
+ double wall_normal_x = (check_blocks[i][0] > map_x) ? -1.0 : 1.0;
+ dot_product = ray_x * wall_normal_x;
+ } else {
+ double wall_normal_y = (check_blocks[i][1] > map_y) ? -1.0 : 1.0;
+ dot_product = ray_y * wall_normal_y;
+ }
+
+ if (dot_product < 0) {
+ hit = true;
+ side = (check_blocks[i][2] == 1);
+ perpWallDist = fmax(0.1, dist_to_wall);
+ close_wall_handled = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!close_wall_handled) {
+ while (!hit && depth < MAX_RENDER_DEPTH) {
+ if (side_x < side_y) {
+ side_x += delta_x;
+ map_x += step_x;
+ side = false;
+ } else {
+ side_y += delta_y;
+ map_y += step_y;
+ side = true;
+ }
+
+ uint8_t block = get_block_at(map_x, map_y);
+ if (block == 0xF) {
+ hit = true;
+ if (side == false) {
+ perpWallDist = (map_x - player.pos.x + (1 - step_x) / 2) / ray_x;
+ } else {
+ perpWallDist = (map_y - player.pos.y + (1 - step_y) / 2) / ray_y;
+ }
+ }
+ depth++;
+ }
+ }
+
+ if (perpWallDist <= 0) perpWallDist = 0.1;
+
+ int start_y = 0;
+ int end_y = RENDER_HEIGHT;
+
+ if (hit) {
+ uint8_t line_height = fmin(255, RENDER_HEIGHT / perpWallDist);
+
+ start_y = view_height / perpWallDist - line_height / 2 + RENDER_HEIGHT / 2;
+ end_y = view_height / perpWallDist + line_height / 2 + RENDER_HEIGHT / 2;
+
+ if (start_y < 0) start_y = 0;
+ if (end_y > RENDER_HEIGHT) end_y = RENDER_HEIGHT;
+
+ uint8_t brightness = fmax(64, 255 - (perpWallDist * 20));
+ uint8_t noise = ((map_x * 17 + map_y * 23) & 0x0F);
+
+ Color wall_color;
+ if (!side) {
+ wall_color = Color(brightness / 4, brightness - noise, brightness / 4);
+ } else {
+ wall_color = Color(brightness / 5, (brightness - noise) * 0.8, brightness / 5);
+ }
+
+ painter.fill_rectangle({x, start_y, RES_DIVIDER, end_y - start_y}, wall_color);
+ }
+
+ if (!full_clear && hit) {
+ if (start_y > 0) {
+ painter.fill_rectangle({x, 0, RES_DIVIDER, start_y}, Color(64, 64, 128));
+ }
+ if (end_y < RENDER_HEIGHT) {
+ painter.fill_rectangle({x, end_y, RES_DIVIDER, RENDER_HEIGHT - end_y}, Color(32, 32, 32));
+ }
+ }
+ }
+}
+
+DoomView::DoomView(NavigationView& nav)
+ : nav_{nav} {
+ SCREEN_WIDTH = screen_width;
+ SCREEN_HEIGHT = screen_height;
+ RENDER_HEIGHT = screen_height - 40;
+ HALF_WIDTH = screen_width / 2;
+ HALF_HEIGHT = RENDER_HEIGHT / 2;
+ add_children({&dummy});
+ game_timer.attach(&game_timer_check, 1.0 / 60.0);
+}
+
+void DoomView::on_show() {
+ dummy.focus();
+}
+
+void DoomView::paint(Painter& painter) {
+ if (!initialized) {
+ initialized = true;
+ std::srand(LPC_RTC->CTIME0);
+ scene = 0;
+ up = down = left = right = fired = false;
+ jogging = view_height = 0;
+ gun_pos = GUN_TARGET_POS;
+ gun_fired = false;
+ num_entities = 0;
+ kills = 0;
+ prev_gun_x = 0;
+ prev_gun_y = 0;
+ painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
+ needs_redraw = true;
+ needs_gun_redraw = false;
+ }
+
+ if (scene == 0) {
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ painter.draw_string({50, 40}, style_yellow, "* * * DOOM * * *");
+ auto style_green = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **");
+ } else if (scene == 1) {
+ if (needs_redraw || (needs_gun_redraw && jogging > 0)) {
+ bool full_clear = (player.velocity == 0 || !prev_velocity_moving);
+ render_map(painter, full_clear);
+ render_entities(painter);
+ render_gun(painter, gun_pos, jogging);
+ painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+ auto style_blue = *ui::Theme::getInstance()->fg_blue;
+ painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health));
+ painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills));
+ painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo));
+ prev_velocity_moving = (player.velocity != 0);
+ needs_redraw = false;
+ needs_gun_redraw = false;
+ } else if (needs_gun_redraw) {
+ int current_x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging;
+ int current_y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging;
+ int flash_height = (gun_pos > GUN_TARGET_POS) ? (gun_pos - GUN_TARGET_POS) * 2 + 10 : 0;
+ int flash_width = (gun_pos > GUN_TARGET_POS) ? GUN_WIDTH + (gun_pos - GUN_TARGET_POS) * 2 : GUN_WIDTH;
+ int min_x = fmin(current_x, prev_gun_x) - 10;
+ int min_y = fmin(current_y, prev_gun_y) - flash_height - 10;
+ int max_x = fmax(current_x, prev_gun_x) + flash_width + 10;
+ int max_y = fmax(current_y, prev_gun_y) + GUN_HEIGHT + 10;
+ min_x = fmax(0, min_x);
+ min_y = fmax(0, min_y);
+ max_x = fmin(SCREEN_WIDTH, max_x);
+ max_y = fmin(RENDER_HEIGHT, max_y);
+ render_map(painter, false, min_x, max_x);
+ render_entities(painter);
+ render_gun(painter, gun_pos, jogging);
+ painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+ auto style_blue = *ui::Theme::getInstance()->fg_blue;
+ painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health));
+ painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills));
+ painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo));
+ needs_gun_redraw = false;
+ }
+ }
+}
+
+void DoomView::frame_sync() {
+ if (scene == 1) {
+ update_game();
+ if (needs_redraw || needs_gun_redraw) set_dirty();
+ }
+}
+
+bool DoomView::on_key(const KeyEvent key) {
+ if (key == KeyEvent::Select && scene == 0) {
+ scene = 1;
+ initialize_level();
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ if (scene == 1) {
+ if (player.health > 0) {
+ if (key == KeyEvent::Up) {
+ up = true;
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ if (key == KeyEvent::Down) {
+ down = true;
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ if (key == KeyEvent::Left) {
+ left = true;
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ if (key == KeyEvent::Right) {
+ right = true;
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ if (key == KeyEvent::Select) {
+ fired = true;
+ set_dirty();
+ return true;
+ }
+ } else if (key == KeyEvent::Select) {
+ scene = 0;
+ initialized = false;
+ needs_redraw = true;
+ set_dirty();
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace ui::external_app::doom
\ No newline at end of file
diff --git a/firmware/application/external/doom/ui_doom.hpp b/firmware/application/external/doom/ui_doom.hpp
new file mode 100644
index 000000000..6fa4fcc9c
--- /dev/null
+++ b/firmware/application/external/doom/ui_doom.hpp
@@ -0,0 +1,84 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_DOOM_H__
+#define __UI_DOOM_H__
+
+#include "ui_widget.hpp"
+#include "ui_navigation.hpp"
+#include "message.hpp"
+
+namespace ui::external_app::doom {
+using Callback = void (*)();
+
+class Ticker {
+ public:
+ Ticker() {}
+ void attach(Callback func, double delay_sec) {
+ game_update_callback = func;
+ game_update_timeout = delay_sec * 60;
+ }
+ void detach() {
+ game_update_callback = nullptr;
+ }
+
+ private:
+ Callback game_update_callback = nullptr;
+ uint32_t game_update_timeout = 0;
+};
+
+extern ui::Painter painter;
+
+enum {
+ White,
+ Blue,
+ Yellow,
+ Purple,
+ Green,
+ Red,
+ Maroon,
+ Orange,
+ Black,
+};
+
+static const Color pp_colors[] = {
+ Color::white(),
+ Color::blue(),
+ Color::yellow(),
+ Color::purple(),
+ Color::green(),
+ Color::red(),
+ Color::magenta(),
+ Color::orange(),
+ Color::black(),
+};
+
+class DoomView : public View {
+ public:
+ DoomView(NavigationView& nav);
+ void on_show() override;
+ std::string title() const override { return "Doom"; }
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_key(const KeyEvent key) override;
+
+ private:
+ NavigationView& nav_;
+ Button dummy{{screen_width, 0, 0, 0}, ""};
+ bool initialized{false};
+ bool prev_velocity_moving{false};
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+} // namespace ui::external_app::doom
+
+#endif /*__UI_DOOM_H__*/
\ No newline at end of file
diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake
index 03fbffad0..8f8385f6a 100644
--- a/firmware/application/external/external.cmake
+++ b/firmware/application/external/external.cmake
@@ -1,9 +1,5 @@
set(EXTCPPSRC
- #tetris
- external/tetris/main.cpp
- external/tetris/ui_tetris.cpp
-
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
@@ -59,6 +55,10 @@ set(EXTCPPSRC
external/keyfob/ui_keyfob.cpp
external/keyfob/ui_keyfob.hpp
+ #tetris
+ external/tetris/main.cpp
+ external/tetris/ui_tetris.cpp
+
#extsensors
external/extsensors/main.cpp
external/extsensors/ui_extsensors.cpp
@@ -107,9 +107,19 @@ set(EXTCPPSRC
external/acars_rx/main.cpp
external/acars_rx/acars_app.cpp
+ #wefax_rx
+ external/wefax_rx/main.cpp
+ external/wefax_rx/ui_wefax_rx.cpp
+
+ #noaaapt_rx
+ external/noaaapt_rx/main.cpp
+ external/noaaapt_rx/ui_noaaapt_rx.cpp
+
+
+
#shoppingcart_lock
external/shoppingcart_lock/main.cpp
- external/shoppingcart_lock/shoppingcart_lock.cpp
+ external/shoppingcart_lock/shoppingcart_lock.cpp
#ookbrute
external/ookbrute/main.cpp
@@ -121,8 +131,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
@@ -134,18 +144,94 @@ 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
+
+ #app_manager
+ external/app_manager/main.cpp
+ external/app_manager/ui_app_manager.cpp
+
+ #hopper
+ external/hopper/main.cpp
+ external/hopper/ui_hopper.cpp
+
+ # whip calculator
+ external/antenna_length/main.cpp
+ external/antenna_length/ui_whipcalc.cpp
+
+ # wav viewer
+ external/wav_view/main.cpp
+ external/wav_view/ui_view_wav.cpp
+
+ # wipe sdcard
+ external/sd_wipe/main.cpp
+ external/sd_wipe/ui_sd_wipe.cpp
+
+ # playlist editor
+ external/playlist_editor/main.cpp
+ external/playlist_editor/ui_playlist_editor.cpp
+
+ #snake
+ external/snake/main.cpp
+ external/snake/ui_snake.cpp
+
+ #stopwatch
+ external/stopwatch/main.cpp
+ external/stopwatch/ui_stopwatch.cpp
+
+ #breakout
+ external/breakout/main.cpp
+ external/breakout/ui_breakout.cpp
+
+ #dinogame
+ external/dinogame/main.cpp
+ external/dinogame/ui_dinogame.cpp
+
+ #doom
+ external/doom/main.cpp
+ external/doom/ui_doom.cpp
+
+ #debug_pmem
+ external/debug_pmem/main.cpp
+ external/debug_pmem/ui_debug_pmem.cpp
+
+ #scanner
+ external/scanner/main.cpp
+ external/scanner/ui_scanner.cpp
+
+ #level
+ external/level/main.cpp
+ external/level/ui_level.cpp
+
+ #gfxEQ
+ external/gfxeq/main.cpp
+ external/gfxeq/ui_gfxeq.cpp
+
+ #detector_rx
+ external/detector_rx/main.cpp
+ external/detector_rx/ui_detector_rx.cpp
+
+ #space_invaders
+ external/spaceinv/main.cpp
+ external/spaceinv/ui_spaceinv.cpp
+
+ #blackjack
+ external/blackjack/main.cpp
+ external/blackjack/ui_blackjack.cpp
+
+ #battleship
+ external/battleship/main.cpp
+ external/battleship/ui_battleship.cpp
)
set(EXTAPPLIST
@@ -177,6 +263,8 @@ set(EXTAPPLIST
#acars_rx
ookbrute
ook_editor
+ wefax_rx
+ noaaapt_rx
shoppingcart_lock
flippertx
remote
@@ -184,4 +272,23 @@ set(EXTAPPLIST
fmradio
tuner
metronome
+ app_manager
+ hopper
+ antenna_length
+ view_wav
+ sd_wipe
+ playlist_editor
+ snake
+ stopwatch
+ breakout
+ dinogame
+ doom
+ debug_pmem
+ scanner
+ level
+ gfxeq
+ detector_rx
+ spaceinv
+ blackjack
+ battleship
)
diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld
index 51a24ea56..1bc6c4be0 100644
--- a/firmware/application/external/external.ld
+++ b/firmware/application/external/external.ld
@@ -26,38 +26,59 @@ MEMORY
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
- ram_external_app_blespam(rwx) : org = 0xADB40000, len = 32k
- ram_external_app_analogtv(rwx) : org = 0xADB50000, len = 32k
- ram_external_app_nrf_rx(rwx) : org = 0xADB60000, len = 32k
- ram_external_app_coasterp(rwx) : org = 0xADB70000, len = 32k
- ram_external_app_lge(rwx) : org = 0xADB80000, len = 32k
- ram_external_app_lcr(rwx) : org = 0xADB90000, len = 32k
- ram_external_app_jammer(rwx) : org = 0xADBA0000, len = 32k
- ram_external_app_gpssim(rwx) : org = 0xADBB0000, len = 32k
- ram_external_app_spainter(rwx) : org = 0xADBC0000, len = 32k
- ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
- ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
- ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
- ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k
- ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k
+ ram_external_app_blespam (rwx) : org = 0xADB40000, len = 32k
+ ram_external_app_analogtv (rwx) : org = 0xADB50000, len = 32k
+ ram_external_app_nrf_rx (rwx) : org = 0xADB60000, len = 32k
+ ram_external_app_coasterp (rwx) : org = 0xADB70000, len = 32k
+ ram_external_app_lge (rwx) : org = 0xADB80000, len = 32k
+ ram_external_app_lcr (rwx) : org = 0xADB90000, len = 32k
+ ram_external_app_jammer (rwx) : org = 0xADBA0000, len = 32k
+ ram_external_app_gpssim (rwx) : org = 0xADBB0000, len = 32k
+ ram_external_app_spainter (rwx) : org = 0xADBC0000, len = 32k
+ ram_external_app_keyfob (rwx) : org = 0xADBD0000, len = 32k
+ ram_external_app_tetris (rwx) : org = 0xADBE0000, len = 32k
+ ram_external_app_extsensors (rwx) : org = 0xADBF0000, len = 32k
+ ram_external_app_foxhunt_rx (rwx) : org = 0xADC00000, len = 32k
+ ram_external_app_audio_test (rwx) : org = 0xADC10000, len = 32k
ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k
- ram_external_app_tpmsrx(rwx) : org = 0xADC30000, len = 32k
- ram_external_app_protoview(rwx) : org = 0xADC40000, len = 32k
- ram_external_app_adsbtx(rwx) : org = 0xADC50000, len = 32k
- ram_external_app_morse_tx(rwx) : org = 0xADC60000, len = 32k
- 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_tuner(rwx) : org = 0xADD20000, len = 32k
- ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
+ ram_external_app_tpmsrx (rwx) : org = 0xADC30000, len = 32k
+ ram_external_app_protoview (rwx) : org = 0xADC40000, len = 32k
+ ram_external_app_adsbtx (rwx) : org = 0xADC50000, len = 32k
+ ram_external_app_morse_tx (rwx) : org = 0xADC60000, len = 32k
+ 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_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_snake (rwx) : org = 0xADDA0000, len = 32k
+ ram_external_app_stopwatch (rwx) : org = 0xADDB0000, len = 32k
+ ram_external_app_wefax_rx (rwx) : org = 0xADDC0000, len = 32k
+ ram_external_app_breakout (rwx) : org = 0xADDD0000, len = 32k
+ ram_external_app_doom (rwx) : org = 0xADDE0000, len = 32k
+ ram_external_app_debug_pmem (rwx) : org = 0xADDF0000, len = 32k
+ ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k
+ ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
+ ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
+ ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
+ ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k
+ ram_external_app_dinogame (rwx) : org = 0xADE50000, len = 32k
+ ram_external_app_spaceinv (rwx) : org = 0xADE60000, len = 32k
+ ram_external_app_blackjack (rwx) : org = 0xADE70000, len = 32k
+ ram_external_app_battleship (rwx) : org = 0xADE80000, len = 32k
}
SECTIONS
@@ -188,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));
@@ -254,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));
@@ -272,4 +292,132 @@ SECTIONS
KEEP(*(.external_app.app_metronome.application_information));
*(*ui*external_app*metronome*);
} > ram_external_app_metronome
+
+ .external_app_app_manager : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_app_manager.application_information));
+ *(*ui*external_app*app_manager*);
+ } > ram_external_app_app_manager
+
+ .external_app_hopper : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_hopper.application_information));
+ *(*ui*external_app*hopper*);
+ } > ram_external_app_hopper
+
+ .external_app_antenna_length : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_antenna_length.application_information));
+ *(*ui*external_app*antenna_length*);
+ } > ram_external_app_antenna_length
+
+ .external_app_view_wav : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_view_wav.application_information));
+ *(*ui*external_app*view_wav*);
+ } > ram_external_app_view_wav
+
+ .external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_sd_wipe.application_information));
+ *(*ui*external_app*sd_wipe*);
+ } > ram_external_app_sd_wipe
+
+ .external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_playlist_editor.application_information));
+ *(*ui*external_app*playlist_editor*);
+ } > ram_external_app_playlist_editor
+
+ .external_app_snake : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_snake.application_information));
+ *(*ui*external_app*snake*);
+ } > ram_external_app_snake
+
+ .external_app_stopwatch : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_stopwatch.application_information));
+ *(*ui*external_app*stopwatch*);
+ } > ram_external_app_stopwatch
+
+ .external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_wefax_rx.application_information));
+ *(*ui*external_app*wefax_rx*);
+ } > ram_external_app_wefax_rx
+
+ .external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_noaaapt_rx.application_information));
+ *(*ui*external_app*noaaapt_rx*);
+ } > ram_external_app_noaaapt_rx
+
+ .external_app_breakout : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_breakout.application_information));
+ *(*ui*external_app*breakout*);
+ } > ram_external_app_breakout
+
+ .external_app_doom : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_doom.application_information));
+ *(*ui*external_app*doom*);
+ } > ram_external_app_doom
+
+ .external_app_debug_pmem : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_debug_pmem.application_information));
+ *(*ui*external_app*debug_pmem*);
+ } > ram_external_app_debug_pmem
+
+ .external_app_scanner : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_scanner.application_information));
+ *(*ui*external_app*scanner*);
+ } > ram_external_app_scanner
+
+ .external_app_level : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_level.application_information));
+ *(*ui*external_app*level*);
+ } > ram_external_app_level
+
+ .external_app_gfxeq : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_gfxeq.application_information));
+ *(*ui*external_app*gfxeq*);
+ } > ram_external_app_gfxeq
+
+ .external_app_detector_rx : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_detector_rx.application_information));
+ *(*ui*external_app*detector_rx*);
+ } > ram_external_app_detector_rx
+
+ .external_app_dinogame : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_dinogame.application_information));
+ *(*ui*external_app*dinogame*);
+ } > ram_external_app_dinogame
+
+ .external_app_spaceinv : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_spaceinv.application_information));
+ *(*ui*external_app*spaceinv*);
+ } > ram_external_app_spaceinv
+
+ .external_app_blackjack : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_blackjack.application_information));
+ *(*ui*external_app*blackjack*);
+ } > ram_external_app_blackjack
+
+ .external_app_battleship : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_battleship.application_information));
+ *(*ui*external_app*battleship*);
+ } > ram_external_app_battleship
+
}
+
diff --git a/firmware/application/external/extsensors/ui_extsensors.hpp b/firmware/application/external/extsensors/ui_extsensors.hpp
index 555e576b3..f222f8a8e 100644
--- a/firmware/application/external/extsensors/ui_extsensors.hpp
+++ b/firmware/application/external/extsensors/ui_extsensors.hpp
@@ -60,7 +60,7 @@ class ExtSensorsView : public View {
{{0 * 8, 5 * 16}, "ORI:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "ENV:", Theme::getInstance()->fg_light->foreground}};
- Text text_info{{0 * 8, 0 * 8, 30 * 8, 16 * 1}, "Connect a compatible module..."};
+ Text text_info{{0 * 8, 0 * 8, screen_width, 16 * 1}, "Connect a compatible module..."};
Text text_gps{{5 * 8, 3 * 16, 24 * 8, 16}, "-"};
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
diff --git a/firmware/application/external/flippertx/ui_flippertx.hpp b/firmware/application/external/flippertx/ui_flippertx.hpp
index a1e058779..b28eca3cc 100644
--- a/firmware/application/external/flippertx/ui_flippertx.hpp
+++ b/firmware/application/external/flippertx/ui_flippertx.hpp
@@ -89,8 +89,6 @@ class FlipperTxView : public View {
std::unique_ptr replay_thread{};
Optional submeta{};
- const std::filesystem::path subghz_dir = u"subghz";
-
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
diff --git a/firmware/application/external/fmradio/main.cpp b/firmware/application/external/fmradio/main.cpp
index 040fbe8c5..8f2f2c98f 100644
--- a/firmware/application/external/fmradio/main.cpp
+++ b/firmware/application/external/fmradio/main.cpp
@@ -38,7 +38,7 @@ __attribute__((section(".external_app.app_fmradio.application_information"), use
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
- /*.app_name = */ "FM Radio",
+ /*.app_name = */ "Radio",
/*.bitmap_data = */ {
0x00,
0x00,
diff --git a/firmware/application/external/fmradio/ui_fmradio.cpp b/firmware/application/external/fmradio/ui_fmradio.cpp
index ece1d2119..73927a89e 100644
--- a/firmware/application/external/fmradio/ui_fmradio.cpp
+++ b/firmware/application/external/fmradio/ui_fmradio.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
+ * Copyright (C) 2025 RocketGod
*
* This file is part of PortaPack.
*
@@ -26,6 +27,7 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
+#include "oversample.hpp"
using namespace portapack;
using namespace modems;
@@ -33,20 +35,118 @@ using namespace ui;
namespace ui::external_app::fmradio {
+#include "external/ui_grapheq.cpi"
+
void FmRadioView::focus() {
field_frequency.focus();
}
+void FmRadioView::show_hide_gfx(bool show) {
+ gr.hidden(!show);
+ gr.set_paused(!show);
+ waveform.set_paused(show);
+ btn_fav_0.hidden(show);
+ btn_fav_1.hidden(show);
+ btn_fav_2.hidden(show);
+ btn_fav_3.hidden(show);
+ btn_fav_4.hidden(show);
+ btn_fav_5.hidden(show);
+ btn_fav_6.hidden(show);
+ btn_fav_7.hidden(show);
+ btn_fav_8.hidden(show);
+ btn_fav_9.hidden(show);
+ txt_save_help.hidden(show);
+ btn_fav_save.hidden(show);
+ field_bw.hidden(show);
+ field_modulation.hidden(show);
+ text_mode_label.hidden(show);
+ set_dirty();
+}
+
+void FmRadioView::change_mode(int32_t mod) {
+ field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; };
+
+ audio::output::stop();
+ receiver_model.disable();
+ baseband::shutdown();
+
+ audio_spectrum_update = false; // Reset spectrum update flag
+ std::fill(audio_spectrum, audio_spectrum + 128, 0); // Clear spectrum buffer
+ waveform.set_dirty();
+ receiver_mode = static_cast(mod);
+ bool is_ssb = (mod == static_cast(ReceiverModel::Mode::AMAudio) &&
+ (field_modulation.selected_index() == 3 || field_modulation.selected_index() == 4));
+
+ switch (mod) {
+ case static_cast(ReceiverModel::Mode::AMAudio):
+ audio_sampling_rate = audio::Rate::Hz_24000; // Increased to 24 kHz for better AM/SSB audio
+ freqman_set_bandwidth_option(0, field_bw); // AM_MODULATION
+ baseband::run_image(portapack::spi_flash::image_tag_am_audio);
+ receiver_mode = ReceiverModel::Mode::AMAudio;
+ field_bw.set_by_value(0); // DSB default
+ receiver_model.set_modulation(receiver_mode);
+ if (is_ssb) {
+ receiver_model.set_am_configuration(field_modulation.selected_index() == 3 ? 1 : 2); // 1=USB, 2=LSB
+ } else {
+ receiver_model.set_am_configuration(0); // DSB
+ }
+ field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
+ radio_bw = index;
+ receiver_model.set_am_configuration(n);
+ };
+ show_hide_gfx(false);
+ break;
+ case static_cast(ReceiverModel::Mode::NarrowbandFMAudio):
+ audio_sampling_rate = audio::Rate::Hz_24000;
+ freqman_set_bandwidth_option(1, field_bw); // NFM_MODULATION
+ baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
+ receiver_mode = ReceiverModel::Mode::NarrowbandFMAudio;
+ field_bw.set_by_value(2); // 16k default
+ receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
+ field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
+ radio_bw = index;
+ receiver_model.set_nbfm_configuration(n);
+ };
+ show_hide_gfx(false);
+ break;
+ case static_cast(ReceiverModel::Mode::WidebandFMAudio):
+ audio_sampling_rate = audio::Rate::Hz_48000;
+ freqman_set_bandwidth_option(2, field_bw); // WFM_MODULATION
+ baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
+ receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
+ field_bw.set_by_value(0); // 200k default
+ receiver_model.set_wfm_configuration(field_bw.selected_index_value());
+ field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
+ radio_bw = index;
+ receiver_model.set_wfm_configuration(n);
+ };
+ break;
+ default:
+ break;
+ }
+
+ receiver_model.set_modulation(receiver_mode);
+
+ receiver_model.set_sampling_rate(3072000);
+ receiver_model.set_baseband_bandwidth(1750000);
+ audio::set_rate(audio_sampling_rate);
+ audio::output::start();
+ receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
+ receiver_model.enable();
+}
+
FmRadioView::FmRadioView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
- add_children({&rssi,
- &field_rf_amp,
+ add_children({&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
+ &field_bw,
+ &text_mode_label,
+ &field_modulation,
&btn_fav_save,
&txt_save_help,
&btn_fav_0,
@@ -60,12 +160,16 @@ FmRadioView::FmRadioView(NavigationView& nav)
&btn_fav_8,
&btn_fav_9,
&audio,
- &waveform});
+ &waveform,
+ &rssi,
+ &gr});
+ txt_save_help.set_focusable(false);
txt_save_help.visible(false);
for (uint8_t i = 0; i < 12; ++i) {
- if (freq_fav_list[i] == 0) {
- freq_fav_list[i] = 87000000;
+ if (freq_fav_list[i].frequency == 0) {
+ freq_fav_list[i].frequency = 87000000;
+ freq_fav_list[i].modulation = static_cast(ReceiverModel::Mode::WidebandFMAudio);
}
}
@@ -73,42 +177,20 @@ FmRadioView::FmRadioView(NavigationView& nav)
field_frequency.set_value(87000000);
}
- receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
-
field_frequency.set_step(25000);
- receiver_model.enable();
- audio::output::start();
+ change_mode(static_cast(ReceiverModel::Mode::WidebandFMAudio));
+ field_modulation.set_by_value(static_cast(ReceiverModel::Mode::WidebandFMAudio));
- btn_fav_0.on_select = [this](Button&) {
- on_btn_clicked(0);
- };
- btn_fav_1.on_select = [this](Button&) {
- on_btn_clicked(1);
- };
- btn_fav_2.on_select = [this](Button&) {
- on_btn_clicked(2);
- };
- btn_fav_3.on_select = [this](Button&) {
- on_btn_clicked(3);
- };
- btn_fav_4.on_select = [this](Button&) {
- on_btn_clicked(4);
- };
- btn_fav_5.on_select = [this](Button&) {
- on_btn_clicked(5);
- };
- btn_fav_6.on_select = [this](Button&) {
- on_btn_clicked(6);
- };
- btn_fav_7.on_select = [this](Button&) {
- on_btn_clicked(7);
- };
- btn_fav_8.on_select = [this](Button&) {
- on_btn_clicked(8);
- };
- btn_fav_9.on_select = [this](Button&) {
- on_btn_clicked(9);
- };
+ btn_fav_0.on_select = [this](Button&) { on_btn_clicked(0); };
+ btn_fav_1.on_select = [this](Button&) { on_btn_clicked(1); };
+ btn_fav_2.on_select = [this](Button&) { on_btn_clicked(2); };
+ btn_fav_3.on_select = [this](Button&) { on_btn_clicked(3); };
+ btn_fav_4.on_select = [this](Button&) { on_btn_clicked(4); };
+ btn_fav_5.on_select = [this](Button&) { on_btn_clicked(5); };
+ btn_fav_6.on_select = [this](Button&) { on_btn_clicked(6); };
+ btn_fav_7.on_select = [this](Button&) { on_btn_clicked(7); };
+ btn_fav_8.on_select = [this](Button&) { on_btn_clicked(8); };
+ btn_fav_9.on_select = [this](Button&) { on_btn_clicked(9); };
btn_fav_save.on_select = [this](Button&) {
save_fav = !save_fav;
@@ -117,20 +199,44 @@ FmRadioView::FmRadioView(NavigationView& nav)
txt_save_help.set_dirty();
};
+ field_modulation.on_change = [this](size_t index, int32_t mod) {
+ change_mode(mod);
+ if (index == 3 || index == 4) { // USB or LSB
+ receiver_model.set_am_configuration(index == 3 ? 1 : 2); // 1=USB, 2=LSB
+ }
+ };
+
+ waveform.on_select = [this](Waveform&) {
+ if (receiver_mode != ReceiverModel::Mode::WidebandFMAudio) { // only there is spectrum message
+ return;
+ }
+ show_hide_gfx(!btn_fav_0.hidden());
+ };
+ gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
+ gr.on_select = [this](GraphEq&) {
+ current_theme = (current_theme + 1) % themes.size();
+ gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
+ gr.set_paused(false);
+ };
update_fav_btn_texts();
+ show_hide_gfx(false);
}
void FmRadioView::on_btn_clicked(uint8_t i) {
if (save_fav) {
save_fav = false;
- freq_fav_list[i] = field_frequency.value();
+ freq_fav_list[i].frequency = field_frequency.value();
+ freq_fav_list[i].modulation = field_modulation.selected_index_value();
+ freq_fav_list[i].bandwidth = radio_bw;
update_fav_btn_texts();
txt_save_help.visible(save_fav);
txt_save_help.set_text("");
txt_save_help.set_dirty();
return;
}
- field_frequency.set_value(freq_fav_list[i]);
+ field_frequency.set_value(freq_fav_list[i].frequency);
+ field_modulation.set_by_value(freq_fav_list[i].modulation);
+ change_mode(freq_fav_list[i].modulation);
}
std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
@@ -141,16 +247,16 @@ std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
}
void FmRadioView::update_fav_btn_texts() {
- btn_fav_0.set_text(to_nice_freq(freq_fav_list[0]));
- btn_fav_1.set_text(to_nice_freq(freq_fav_list[1]));
- btn_fav_2.set_text(to_nice_freq(freq_fav_list[2]));
- btn_fav_3.set_text(to_nice_freq(freq_fav_list[3]));
- btn_fav_4.set_text(to_nice_freq(freq_fav_list[4]));
- btn_fav_5.set_text(to_nice_freq(freq_fav_list[5]));
- btn_fav_6.set_text(to_nice_freq(freq_fav_list[6]));
- btn_fav_7.set_text(to_nice_freq(freq_fav_list[7]));
- btn_fav_8.set_text(to_nice_freq(freq_fav_list[8]));
- btn_fav_9.set_text(to_nice_freq(freq_fav_list[9]));
+ btn_fav_0.set_text(to_nice_freq(freq_fav_list[0].frequency));
+ btn_fav_1.set_text(to_nice_freq(freq_fav_list[1].frequency));
+ btn_fav_2.set_text(to_nice_freq(freq_fav_list[2].frequency));
+ btn_fav_3.set_text(to_nice_freq(freq_fav_list[3].frequency));
+ btn_fav_4.set_text(to_nice_freq(freq_fav_list[4].frequency));
+ btn_fav_5.set_text(to_nice_freq(freq_fav_list[5].frequency));
+ btn_fav_6.set_text(to_nice_freq(freq_fav_list[6].frequency));
+ btn_fav_7.set_text(to_nice_freq(freq_fav_list[7].frequency));
+ btn_fav_8.set_text(to_nice_freq(freq_fav_list[8].frequency));
+ btn_fav_9.set_text(to_nice_freq(freq_fav_list[9].frequency));
}
FmRadioView::~FmRadioView() {
@@ -160,9 +266,17 @@ FmRadioView::~FmRadioView() {
}
void FmRadioView::on_audio_spectrum() {
- for (size_t i = 0; i < audio_spectrum_data->db.size(); i++)
- audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
- waveform.set_dirty();
+ if (gr.visible() && audio_spectrum_data) gr.update_audio_spectrum(*audio_spectrum_data);
+ if (audio_spectrum_data && audio_spectrum_data->db.size() <= 128) {
+ for (size_t i = 0; i < audio_spectrum_data->db.size(); ++i) {
+ audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
+ }
+ waveform.set_dirty();
+ } else {
+ // Fallback: Clear waveform if no valid data
+ std::fill(audio_spectrum, audio_spectrum + 128, 0);
+ waveform.set_dirty();
+ }
}
} // namespace ui::external_app::fmradio
diff --git a/firmware/application/external/fmradio/ui_fmradio.hpp b/firmware/application/external/fmradio/ui_fmradio.hpp
index 74fc65eaa..4cad10c2a 100644
--- a/firmware/application/external/fmradio/ui_fmradio.hpp
+++ b/firmware/application/external/fmradio/ui_fmradio.hpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
+ * Copyright (C) 2025 RocketGod
*
* This file is part of PortaPack.
*
@@ -38,11 +39,16 @@
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
+#include "audio.hpp"
+#include "freqman_db.hpp"
+#include "ui_freqman.hpp"
using namespace ui;
namespace ui::external_app::fmradio {
+#include "external/ui_grapheq.hpp"
+
#define FMR_BTNGRID_TOP 60
class FmRadioView : public View {
@@ -54,31 +60,65 @@ class FmRadioView : public View {
void focus() override;
- std::string title() const override { return "FM radio"; };
+ std::string title() const override { return "Radio"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
int16_t audio_spectrum[128]{0};
bool audio_spectrum_update = false;
+ ReceiverModel::Mode receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
AudioSpectrum* audio_spectrum_data{nullptr};
- rf::Frequency freq_fav_list[12] = {0};
-
+ struct Favorite {
+ rf::Frequency frequency = 0;
+ int32_t modulation = static_cast(ReceiverModel::Mode::WidebandFMAudio);
+ uint8_t bandwidth = 0;
+ };
+ Favorite freq_fav_list[12];
+ audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
+ uint8_t radio_bw = 0;
+ uint32_t current_theme{0};
app_settings::SettingsManager settings_{
"rx_fmradio",
app_settings::Mode::RX,
- {{"favlist0"sv, &freq_fav_list[0]},
- {"favlist1"sv, &freq_fav_list[1]},
- {"favlist2"sv, &freq_fav_list[2]},
- {"favlist3"sv, &freq_fav_list[3]},
- {"favlist4"sv, &freq_fav_list[4]},
- {"favlist5"sv, &freq_fav_list[5]},
- {"favlist6"sv, &freq_fav_list[6]},
- {"favlist7"sv, &freq_fav_list[7]},
- {"favlist8"sv, &freq_fav_list[8]},
- {"favlist9"sv, &freq_fav_list[9]},
- {"favlist10"sv, &freq_fav_list[10]},
- {"favlist11"sv, &freq_fav_list[11]}}};
+ {{"favlist0_freq"sv, &freq_fav_list[0].frequency},
+ {"favlist1_freq"sv, &freq_fav_list[1].frequency},
+ {"favlist2_freq"sv, &freq_fav_list[2].frequency},
+ {"favlist3_freq"sv, &freq_fav_list[3].frequency},
+ {"favlist4_freq"sv, &freq_fav_list[4].frequency},
+ {"favlist5_freq"sv, &freq_fav_list[5].frequency},
+ {"favlist6_freq"sv, &freq_fav_list[6].frequency},
+ {"favlist7_freq"sv, &freq_fav_list[7].frequency},
+ {"favlist8_freq"sv, &freq_fav_list[8].frequency},
+ {"favlist9_freq"sv, &freq_fav_list[9].frequency},
+ {"favlist10_freq"sv, &freq_fav_list[10].frequency},
+ {"favlist11_freq"sv, &freq_fav_list[11].frequency},
+ {"favlist0_mod"sv, &freq_fav_list[0].modulation},
+ {"favlist1_mod"sv, &freq_fav_list[1].modulation},
+ {"favlist2_mod"sv, &freq_fav_list[2].modulation},
+ {"favlist3_mod"sv, &freq_fav_list[3].modulation},
+ {"favlist4_mod"sv, &freq_fav_list[4].modulation},
+ {"favlist5_mod"sv, &freq_fav_list[5].modulation},
+ {"favlist6_mod"sv, &freq_fav_list[6].modulation},
+ {"favlist7_mod"sv, &freq_fav_list[7].modulation},
+ {"favlist8_mod"sv, &freq_fav_list[8].modulation},
+ {"favlist9_mod"sv, &freq_fav_list[9].modulation},
+ {"favlist10_mod"sv, &freq_fav_list[10].modulation},
+ {"favlist11_mod"sv, &freq_fav_list[11].modulation},
+ {"favlist0_bw"sv, &freq_fav_list[0].bandwidth},
+ {"favlist1_bw"sv, &freq_fav_list[1].bandwidth},
+ {"favlist2_bw"sv, &freq_fav_list[2].bandwidth},
+ {"favlist3_bw"sv, &freq_fav_list[3].bandwidth},
+ {"favlist4_bw"sv, &freq_fav_list[4].bandwidth},
+ {"favlist5_bw"sv, &freq_fav_list[5].bandwidth},
+ {"favlist6_bw"sv, &freq_fav_list[6].bandwidth},
+ {"favlist7_bw"sv, &freq_fav_list[7].bandwidth},
+ {"favlist8_bw"sv, &freq_fav_list[8].bandwidth},
+ {"favlist9_bw"sv, &freq_fav_list[9].bandwidth},
+ {"favlist10_bw"sv, &freq_fav_list[10].bandwidth},
+ {"favlist11_bw"sv, &freq_fav_list[11].bandwidth},
+ {"radio_bw"sv, &radio_bw},
+ {"theme"sv, ¤t_theme}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@@ -89,12 +129,30 @@ class FmRadioView : public View {
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
+ OptionsField field_bw{
+ {10 * 8, FMR_BTNGRID_TOP + 6 * 34},
+ 6,
+ {}};
+
+ Text text_mode_label{
+ {20 * 8, FMR_BTNGRID_TOP + 6 * 34, 5 * 8, 1 * 28},
+ "MODE:"};
+
+ OptionsField field_modulation{
+ {26 * 8, FMR_BTNGRID_TOP + 6 * 34},
+ 4,
+ {{"AM", static_cast(ReceiverModel::Mode::AMAudio)},
+ {"NFM", static_cast(ReceiverModel::Mode::NarrowbandFMAudio)},
+ {"WFM", static_cast(ReceiverModel::Mode::WidebandFMAudio)},
+ {"USB", static_cast(ReceiverModel::Mode::AMAudio)},
+ {"LSB", static_cast(ReceiverModel::Mode::AMAudio)}}};
+
TextField txt_save_help{
{2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
" "};
@@ -103,12 +161,15 @@ class FmRadioView : public View {
{21 * 8, 10, 6 * 8, 4}};
Waveform waveform{
- {0, 20, 30 * 8, 2 * 16},
+ {0, 20, UI_POS_MAXWIDTH, 2 * 16},
audio_spectrum,
128,
0,
false,
- Theme::getInstance()->bg_darkest->foreground};
+ Theme::getInstance()->bg_darkest->foreground,
+ true};
+
+ GraphEq gr{{2, FMR_BTNGRID_TOP, UI_POS_MAXWIDTH - 4, UI_POS_MAXHEIGHT - FMR_BTNGRID_TOP}, true};
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
@@ -123,10 +184,41 @@ class FmRadioView : public View {
Button btn_fav_save{{2, FMR_BTNGRID_TOP + 6 * 34, 7 * 8, 1 * 28}, "Save"};
bool save_fav = false;
+
void on_btn_clicked(uint8_t i);
void update_fav_btn_texts();
std::string to_nice_freq(rf::Frequency freq);
void on_audio_spectrum();
+ void change_mode(int32_t mod);
+
+ void show_hide_gfx(bool show);
+
+ struct ColorTheme {
+ Color base_color;
+ Color peak_color;
+ };
+
+ const std::array themes{
+ ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
+ ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
+ ColorTheme{Color(0, 0, 255), Color(255, 255, 0)},
+ ColorTheme{Color(255, 128, 0), Color(255, 0, 128)},
+ ColorTheme{Color(128, 0, 255), Color(0, 255, 255)},
+ ColorTheme{Color(255, 255, 0), Color(0, 255, 128)},
+ ColorTheme{Color(255, 0, 0), Color(0, 128, 255)},
+ ColorTheme{Color(0, 255, 128), Color(255, 128, 255)},
+ ColorTheme{Color(128, 128, 128), Color(255, 255, 255)},
+ ColorTheme{Color(255, 64, 0), Color(0, 255, 64)},
+ ColorTheme{Color(0, 128, 128), Color(255, 192, 0)},
+ ColorTheme{Color(0, 255, 0), Color(0, 128, 0)},
+ ColorTheme{Color(32, 64, 32), Color(0, 255, 0)},
+ ColorTheme{Color(64, 0, 128), Color(255, 0, 255)},
+ ColorTheme{Color(0, 64, 0), Color(0, 255, 128)},
+ ColorTheme{Color(255, 255, 255), Color(0, 0, 255)},
+ ColorTheme{Color(128, 0, 0), Color(255, 128, 0)},
+ ColorTheme{Color(0, 128, 255), Color(255, 255, 128)},
+ ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
+ ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
MessageHandlerRegistration message_handler_audio_spectrum{
Message::ID::AudioSpectrum,
diff --git a/firmware/application/external/font_viewer/ui_font_viewer.cpp b/firmware/application/external/font_viewer/ui_font_viewer.cpp
index b78fbcff9..7b95a3bbd 100644
--- a/firmware/application/external/font_viewer/ui_font_viewer.cpp
+++ b/firmware/application/external/font_viewer/ui_font_viewer.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
+ * copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -32,7 +33,7 @@ namespace ui::external_app::font_viewer {
/* DebugFontsView *******************************************************/
-uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
+uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font) {
auto char_width{font_style->font.char_width()};
auto char_height{font_style->font.line_height()};
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
@@ -47,7 +48,15 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
if ((c % cpl) == 0)
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
- painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
+ Style highlight_style_big = *Theme::getInstance()->fg_red;
+ Style highlight_style_small = *Theme::getInstance()->bg_important_small;
+ if (c == field_cursor.value()) {
+ painter.draw_char({((c % cpl) + 5) * char_width, line_pos},
+ is_big_font ? highlight_style_big : highlight_style_small,
+ (char)(c + 0x20));
+ } else {
+ painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
+ }
}
return line_pos + char_height;
@@ -56,13 +65,43 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
void DebugFontsView::paint(Painter& painter) {
int16_t line_pos;
- line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16");
- display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8");
+ line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16", true);
+ display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8", false);
+ paint_zoomed_text(painter);
+}
+
+void DebugFontsView::update_address_text() {
+ uint8_t ascii_value = field_cursor.value() + 0x20;
+ text_address.set("0x" + to_string_hex(ascii_value, 2));
+}
+
+void DebugFontsView::paint_zoomed_text(Painter& painter) {
+ if (field_zoom_level.value() == 0) return;
+ uint8_t cursor_pos = field_cursor.value();
+ painter.draw_char({screen_width / 2, screen_height / 2},
+ *Theme::getInstance()->bg_darkest,
+ (char)(cursor_pos + 0x20), field_zoom_level.value());
}
DebugFontsView::DebugFontsView(NavigationView& nav)
: nav_{nav} {
+ add_children({&field_cursor,
+ &text_address,
+ &field_zoom_level});
set_focusable(true);
+
+ field_cursor.on_change = [&](int32_t) {
+ update_address_text();
+ set_dirty();
+ };
+
+ field_zoom_level.on_change = [&](int32_t) {
+ set_dirty();
+ };
+}
+
+void DebugFontsView::focus() {
+ field_cursor.focus();
}
} /* namespace ui::external_app::font_viewer */
diff --git a/firmware/application/external/font_viewer/ui_font_viewer.hpp b/firmware/application/external/font_viewer/ui_font_viewer.hpp
index 72afcb58b..73347f0a4 100644
--- a/firmware/application/external/font_viewer/ui_font_viewer.hpp
+++ b/firmware/application/external/font_viewer/ui_font_viewer.hpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
+ * copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -39,10 +40,30 @@ class DebugFontsView : public View {
public:
DebugFontsView(NavigationView& nav);
void paint(Painter& painter) override;
+ void focus() override;
std::string title() const override { return "Fonts"; };
private:
- uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
+ uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font);
+ void update_address_text();
+ void paint_zoomed_text(Painter& painter);
+
+ NumberField field_cursor{
+ {0 * 8, 0 * 8},
+ 4,
+ {0, 1000},
+ 1,
+ ' '};
+ NumberField field_zoom_level{
+ {6 * 8, 0 * 8},
+ 4,
+ {0, 1000},
+ 1,
+ ' '};
+ Text text_address{
+ {screen_width / 2, 0 * 16, screen_width / 2, 16},
+ "0x20",
+ };
NavigationView& nav_;
};
diff --git a/firmware/application/external/foxhunt/ui_foxhunt_rx.hpp b/firmware/application/external/foxhunt/ui_foxhunt_rx.hpp
index ed61336fd..25d48c0c3 100644
--- a/firmware/application/external/foxhunt/ui_foxhunt_rx.hpp
+++ b/firmware/application/external/foxhunt/ui_foxhunt_rx.hpp
@@ -62,7 +62,7 @@ class FoxhuntRxView : public View {
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -73,7 +73,7 @@ class FoxhuntRxView : public View {
{0 * 8, 2 * 16 + 4, 14 * 8, 14},
};
RSSIGraph rssi_graph{
- {0, 50, 240, 30},
+ {0, 50, screen_width, 30},
};
Button clear_markers{
@@ -84,7 +84,7 @@ class FoxhuntRxView : public View {
{2, 18, 7 * 8, 16},
"Mark"};
- GeoMap geomap{{0, 80, 240, 240}};
+ GeoMap geomap{{0, 80, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
diff --git a/firmware/application/external/gfxeq/main.cpp b/firmware/application/external/gfxeq/main.cpp
new file mode 100644
index 000000000..984e55be8
--- /dev/null
+++ b/firmware/application/external/gfxeq/main.cpp
@@ -0,0 +1,36 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_gfxeq.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::gfxeq {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::gfxeq
+
+extern "C" {
+__attribute__((section(".external_app.app_gfxeq.application_information"), used)) application_information_t _application_information_gfxeq = {
+ (uint8_t*)0x00000000,
+ ui::external_app::gfxeq::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+ "gfxEQ",
+ {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ ui::Color::green().v,
+ app_location_t::RX,
+ -1,
+ {'P', 'W', 'F', 'M'},
+ 0x00000000,
+};
+
+} // namespace ui::external_app::gfxeq
diff --git a/firmware/application/external/gfxeq/ui_gfxeq.cpp b/firmware/application/external/gfxeq/ui_gfxeq.cpp
new file mode 100644
index 000000000..51f1ca99a
--- /dev/null
+++ b/firmware/application/external/gfxeq/ui_gfxeq.cpp
@@ -0,0 +1,98 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_gfxeq.hpp"
+#include "ui.hpp"
+#include "ui_freqman.hpp"
+#include "tone_key.hpp"
+#include "analog_audio_app.hpp"
+#include "portapack.hpp"
+#include "audio.hpp"
+#include "baseband_api.hpp"
+#include "dsp_fir_taps.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::gfxeq {
+
+#include "external/ui_grapheq.cpi"
+
+gfxEQView::gfxEQView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&button_frequency, &field_rf_amp, &field_lna, &field_vga,
+ &button_mood, &field_volume, &gr});
+
+ audio::output::stop();
+ receiver_model.disable();
+ baseband::shutdown();
+
+ baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
+
+ receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
+ receiver_model.set_wfm_configuration(1); // 200k => 0 , 180k => 1 , 80k => 2. Set to 1 or 2 for better reception
+ receiver_model.set_sampling_rate(3072000);
+ receiver_model.set_baseband_bandwidth(1750000);
+
+ audio::set_rate(audio::Rate::Hz_48000);
+ audio::output::start();
+ receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
+ //
+ receiver_model.enable();
+
+ receiver_model.set_target_frequency(frequency_value); // Retune to actual freq
+ button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
+
+ button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
+ auto new_view = nav_.push(frequency_value);
+ new_view->on_changed = [this, &button](rf::Frequency f) {
+ frequency_value = f;
+ receiver_model.set_target_frequency(f); // Retune to actual freq
+ button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
+ };
+ };
+
+ button_frequency.on_change = [this]() {
+ int64_t def_step = 25000;
+ frequency_value = frequency_value + (button_frequency.get_encoder_delta() * def_step);
+ if (frequency_value < 1) {
+ frequency_value = 1;
+ }
+ if (frequency_value > (MAX_UFREQ - def_step)) {
+ frequency_value = MAX_UFREQ;
+ }
+ button_frequency.set_encoder_delta(0);
+ receiver_model.set_target_frequency(frequency_value); // Retune to actual freq
+ button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
+ };
+
+ button_mood.on_select = [this](Button&) { this->cycle_theme(); };
+ gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
+}
+
+// needed to answer usb serial frequency set
+void gfxEQView::on_freqchg(int64_t freq) {
+ receiver_model.set_target_frequency(freq); // Retune to actual freq
+ button_frequency.set_text("<" + to_string_short_freq(freq) + ">");
+}
+
+gfxEQView::~gfxEQView() {
+ audio::output::stop();
+ receiver_model.disable();
+ baseband::shutdown();
+}
+
+void gfxEQView::focus() {
+ button_frequency.focus();
+}
+
+void gfxEQView::cycle_theme() {
+ current_theme = (current_theme + 1) % themes.size();
+ gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
+}
+
+} // namespace ui::external_app::gfxeq
\ No newline at end of file
diff --git a/firmware/application/external/gfxeq/ui_gfxeq.hpp b/firmware/application/external/gfxeq/ui_gfxeq.hpp
new file mode 100644
index 000000000..a94ec7e20
--- /dev/null
+++ b/firmware/application/external/gfxeq/ui_gfxeq.hpp
@@ -0,0 +1,108 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_GFXEQ_HPP__
+#define __UI_GFXEQ_HPP__
+
+#include "ui_widget.hpp"
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "message.hpp"
+#include "baseband_api.hpp"
+#include "portapack.hpp"
+#include "ui_spectrum.hpp"
+#include "ui_freq_field.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+
+namespace ui::external_app::gfxeq {
+
+#include "external/ui_grapheq.hpp"
+
+class gfxEQView : public View {
+ public:
+ gfxEQView(NavigationView& nav);
+ ~gfxEQView();
+
+ gfxEQView(const gfxEQView&) = delete;
+ gfxEQView& operator=(const gfxEQView&) = delete;
+
+ void focus() override;
+ std::string title() const override { return "gfxEQ"; }
+
+ void on_freqchg(int64_t freq);
+
+ private:
+ struct ColorTheme {
+ Color base_color;
+ Color peak_color;
+ };
+
+ NavigationView& nav_;
+
+ uint32_t current_theme{0};
+ const std::array themes{
+ ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
+ ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
+ ColorTheme{Color(0, 0, 255), Color(255, 255, 0)},
+ ColorTheme{Color(255, 128, 0), Color(255, 0, 128)},
+ ColorTheme{Color(128, 0, 255), Color(0, 255, 255)},
+ ColorTheme{Color(255, 255, 0), Color(0, 255, 128)},
+ ColorTheme{Color(255, 0, 0), Color(0, 128, 255)},
+ ColorTheme{Color(0, 255, 128), Color(255, 128, 255)},
+ ColorTheme{Color(128, 128, 128), Color(255, 255, 255)},
+ ColorTheme{Color(255, 64, 0), Color(0, 255, 64)},
+ ColorTheme{Color(0, 128, 128), Color(255, 192, 0)},
+ ColorTheme{Color(0, 255, 0), Color(0, 128, 0)},
+ ColorTheme{Color(32, 64, 32), Color(0, 255, 0)},
+ ColorTheme{Color(64, 0, 128), Color(255, 0, 255)},
+ ColorTheme{Color(0, 64, 0), Color(0, 255, 128)},
+ ColorTheme{Color(255, 255, 255), Color(0, 0, 255)},
+ ColorTheme{Color(128, 0, 0), Color(255, 128, 0)},
+ ColorTheme{Color(0, 128, 255), Color(255, 255, 128)},
+ ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
+ ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
+
+ ButtonWithEncoder button_frequency{{0 * 8, 0 * 16 + 4, 11 * 8, 1 * 8}, ""};
+ RFAmpField field_rf_amp{{13 * 8, 0 * 16}};
+ LNAGainField field_lna{{15 * 8, 0 * 16}};
+ VGAGainField field_vga{{18 * 8, 0 * 16}};
+ Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
+ AudioVolumeField field_volume{{screen_width - 2 * 8, 0 * 16}};
+ GraphEq gr{{2, UI_POS_DEFAULT_HEIGHT, UI_POS_MAXWIDTH - 4, UI_POS_HEIGHT_REMAINING(2)}, false};
+
+ rf::Frequency frequency_value{93100000};
+
+ RxRadioState rx_radio_state_{};
+
+ app_settings::SettingsManager settings_{
+ "rx_gfx_eq",
+ app_settings::Mode::RX,
+ {{"theme", ¤t_theme},
+ {"frequency", &frequency_value}}};
+
+ void cycle_theme();
+
+ MessageHandlerRegistration message_handler_audio_spectrum{
+ Message::ID::AudioSpectrum,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ this->gr.update_audio_spectrum(*message.data);
+ }};
+
+ MessageHandlerRegistration message_handler_freqchg{
+ Message::ID::FreqChangeCommand,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ this->on_freqchg(message->freq);
+ }};
+};
+
+} // namespace ui::external_app::gfxeq
+
+#endif
\ No newline at end of file
diff --git a/firmware/application/external/gpssim/gps_sim_app.hpp b/firmware/application/external/gpssim/gps_sim_app.hpp
index ba967177d..42b4906ea 100644
--- a/firmware/application/external/gpssim/gps_sim_app.hpp
+++ b/firmware/application/external/gpssim/gps_sim_app.hpp
@@ -112,7 +112,7 @@ class GpsSimAppView : public View {
LanguageHelper::currentMessages[LANG_LOOP],
true};
ImageButton button_play{
- {28 * 8, 2 * 16, 2 * 8, 1 * 16},
+ {screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
diff --git a/firmware/application/external/hopper/main.cpp b/firmware/application/external/hopper/main.cpp
new file mode 100644
index 000000000..16412fdd6
--- /dev/null
+++ b/firmware/application/external/hopper/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_hopper.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::hopper {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::hopper
+
+extern "C" {
+
+__attribute__((section(".external_app.app_hopper.application_information"), used)) application_information_t _application_information_hopper = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::hopper::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Hopper",
+ /*.bitmap_data = */ {
+ 0xE0,
+ 0x07,
+ 0xF8,
+ 0x1F,
+ 0x1C,
+ 0x38,
+ 0x0E,
+ 0x78,
+ 0x06,
+ 0x7C,
+ 0x03,
+ 0xCE,
+ 0x03,
+ 0xC7,
+ 0x83,
+ 0xC3,
+ 0xC3,
+ 0xC1,
+ 0xE3,
+ 0xC0,
+ 0x73,
+ 0xC0,
+ 0x3E,
+ 0x60,
+ 0x1E,
+ 0x70,
+ 0x1C,
+ 0x38,
+ 0xF8,
+ 0x1F,
+ 0xE0,
+ 0x07,
+ },
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/hopper/ui_hopper.cpp b/firmware/application/external/hopper/ui_hopper.cpp
new file mode 100644
index 000000000..fd8a983ad
--- /dev/null
+++ b/firmware/application/external/hopper/ui_hopper.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2016 Furrtek
+ * Copyright (C) 2020 euquiq
+ * copyleft mr.r0b0t from the F society
+ *
+ * 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_hopper.hpp"
+#include "ui_receiver.hpp"
+#include "ui_freqman.hpp"
+#include "ui_fileman.hpp"
+#include "file_path.hpp"
+#include "file_reader.hpp"
+#include "convert.hpp"
+
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::hopper {
+
+void HopperView::focus() {
+ button_load_list.focus();
+}
+
+HopperView::~HopperView() {
+ transmitter_model.disable();
+ baseband::shutdown();
+}
+
+void HopperView::update_freq_list_menu_view() {
+ menu_freq_list.clear();
+ uint8_t list_count = freq_list.size();
+
+ if (list_count == 0) {
+ menu_freq_list.add_item({"Add freq or load list",
+ Theme::getInstance()->bg_darkest->foreground,
+ nullptr,
+ nullptr});
+ }
+
+ for (uint8_t i = 0; i < list_count; i++) {
+ menu_freq_list.add_item({to_string_rounded_freq(freq_list[i], INT8_MAX),
+ Theme::getInstance()->bg_darkest->foreground,
+ nullptr,
+ nullptr});
+ }
+
+ set_dirty();
+}
+
+void HopperView::on_retune(const rf::Frequency freq, const uint32_t range) {
+ if (freq) {
+ transmitter_model.set_target_frequency(freq);
+ text_range_number.set(to_string_dec_uint(range, 2));
+ }
+}
+
+void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) {
+ hopper_channels[i].enabled = true;
+ hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
+ hopper_channels[i].center = center;
+ hopper_channels[i].duration = duration ? 3072 * duration : 10;
+}
+
+void HopperView::start_tx() {
+ uint32_t c, i = 0;
+ bool out_of_ranges = false;
+
+ size_t hop_value = options_hop.selected_index_value();
+
+ // Disable all channels by default
+ for (c = 0; c < JAMMER_MAX_CH; c++)
+ hopper_channels[c].enabled = false;
+
+ uint8_t channel_count = freq_list.size();
+
+ if (channel_count <= JAMMER_MAX_CH) {
+ for (c = 0; c < channel_count; c++) {
+ if (i >= JAMMER_MAX_CH) {
+ out_of_ranges = true;
+ break;
+ }
+ set_hopper_channel(i, JAMMER_CH_WIDTH, freq_list[c], hop_value);
+ i++;
+ }
+ } else {
+ out_of_ranges = true;
+ }
+
+ if (!out_of_ranges && i) {
+ text_range_total.set("/" + to_string_dec_uint(i, 2));
+
+ jamming = true;
+ button_transmit.set_style(&style_cancel);
+ button_transmit.set_text("STOP");
+
+ transmitter_model.set_rf_amp(field_amp.value());
+ transmitter_model.set_tx_gain(field_gain.value());
+ transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
+ transmitter_model.enable();
+
+ baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+ mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
+ } else {
+ if (out_of_ranges)
+ nav_.display_modal("Error", "Jam freq too much.");
+ }
+}
+
+void HopperView::stop_tx() {
+ button_transmit.set_style(&style_val);
+ button_transmit.set_text("START");
+ transmitter_model.disable();
+ baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
+ jamming = false;
+ cooling = false;
+}
+
+// called each 1/60th of second
+void HopperView::on_timer() {
+ if (++mscounter == 60) {
+ mscounter = 0;
+ if (jamming) {
+ if (cooling) {
+ if (++seconds >= field_timepause.value()) { // Re-start TX
+ transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
+ transmitter_model.enable();
+ button_transmit.set_text("STOP");
+ baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+
+ int32_t jitter_amount = field_jitter.value();
+ if (jitter_amount) {
+ lfsr_v = lfsr_iterate(lfsr_v);
+ jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
+ mscounter += jitter_amount;
+ }
+
+ cooling = false;
+ seconds = 0;
+ }
+ } else {
+ if (++seconds >= field_timetx.value()) // Start cooling period:
+ {
+ transmitter_model.disable();
+ button_transmit.set_text("PAUSED");
+ baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
+
+ int32_t jitter_amount = field_jitter.value();
+ if (jitter_amount) {
+ lfsr_v = lfsr_iterate(lfsr_v);
+ jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
+ mscounter += jitter_amount;
+ }
+
+ cooling = true;
+ seconds = 0;
+ }
+ }
+ }
+ }
+}
+
+void HopperView::load_list() {
+ freq_list.clear();
+
+ auto open_view = nav_.push(".PHOP");
+ open_view->push_dir(hopper_dir);
+ open_view->on_changed = [this](std::filesystem::path path) {
+ File f;
+ auto error = f.open(path);
+ if (error) {
+ nav_.display_modal("Err", "Can't open.");
+ update_freq_list_menu_view();
+ return;
+ }
+
+ freq_list.clear();
+
+ auto reader = FileLineReader(f);
+ for (const auto& line : reader) {
+ if (line.length() == 0 || line[0] == '#')
+ continue;
+
+ rf::Frequency freq;
+ if (parse_int(line, freq)) {
+ if (freq_list.size() >= JAMMER_MAX_CH) {
+ break;
+ }
+ freq_list.push_back(freq);
+ }
+ }
+ update_freq_list_menu_view();
+ };
+
+ update_freq_list_menu_view();
+}
+
+void HopperView::save_list() {
+ if (freq_list.empty()) {
+ nav_.display_modal("Err", "Nothing to save");
+ return;
+ }
+
+ ensure_directory(hopper_dir);
+
+ filename_buffer = "";
+ text_prompt(
+ nav_,
+ filename_buffer,
+ 64,
+ ENTER_KEYBOARD_MODE_ALPHA,
+ [this](std::string& value) {
+ auto path = hopper_dir / (value + ".PHOP");
+
+ File f;
+ auto error = f.create(path);
+ if (error) {
+ nav_.display_modal("Err", "Create fail.");
+ return;
+ }
+
+ for (const auto& freq : freq_list) {
+ auto freq_str = to_string_dec_uint(freq) + "\n";
+ f.write(freq_str.c_str(), freq_str.length());
+ }
+
+ text_range_number.set("Saved: " + path.filename().string());
+ });
+}
+
+HopperView::HopperView(
+ NavigationView& nav)
+ : nav_{nav} {
+ // baseband::run_image(portapack::spi_flash::image_tag_jammer);
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+
+ add_children({&menu_freq_list,
+ &button_load_list,
+ &button_add_freq,
+ &button_delete_freq,
+ &button_save_list,
+ &button_clear,
+ &labels,
+ &options_type,
+ &text_range_number,
+ &text_range_total,
+ &options_speed,
+ &options_hop,
+ &field_timetx,
+ &field_timepause,
+ &field_jitter,
+ &field_gain,
+ &field_amp,
+ &button_transmit});
+
+ options_type.set_selected_index(3); // Rand CW
+ options_speed.set_selected_index(3); // 10kHz
+ options_hop.set_selected_index(3); // 50ms
+ button_transmit.set_style(&style_val);
+
+ field_timetx.set_value(30);
+ field_timepause.set_value(1);
+ field_gain.set_value(transmitter_model.tx_gain());
+ field_amp.set_value(transmitter_model.rf_amp());
+
+ button_load_list.on_select = [this]() {
+ load_list();
+ update_freq_list_menu_view();
+ };
+
+ button_add_freq.on_select = [this]() {
+ if (freq_list.size() < JAMMER_MAX_CH) {
+ auto kb_view = nav_.push(0);
+ kb_view->on_changed = [this](rf::Frequency freq) {
+ freq_list.push_back(freq);
+ update_freq_list_menu_view();
+ };
+
+ } else {
+ nav_.display_modal("Err", "No more.");
+ }
+ update_freq_list_menu_view();
+ };
+
+ button_delete_freq.on_select = [this]() {
+ auto i = menu_freq_list.highlighted_index();
+ if (i < freq_list.size()) {
+ freq_list.erase(freq_list.begin() + i);
+ }
+ update_freq_list_menu_view();
+ };
+
+ button_save_list.on_select = [this]() {
+ save_list();
+ update_freq_list_menu_view();
+ };
+
+ button_clear.on_select = [this]() {
+ // clang-format off
+ nav_.display_modal("Del:", "Clean all?\n", YESNO, [this](bool choice) {
+ if (choice){
+ freq_list.clear();
+ update_freq_list_menu_view();
+ } }, TRUE);
+ // clang-format on
+
+ update_freq_list_menu_view();
+ };
+
+ button_transmit.on_select = [this](Button&) {
+ if (jamming || cooling) {
+ stop_tx();
+ } else {
+ // if hop speed is 0, alert the user that this will cause a freeze on UI
+ if (options_hop.selected_index_value() == 0) {
+ nav_.display_modal(
+ "Warning", "Hopping set to 0ms (fastest).\n\nTHIS WILL FREEZE THE HACKRF,\npress RESET button to stop\n\nAre you sure?", YESNO, [this](bool choice) {
+ if (choice) {
+ // Wait for UI update before the freeze
+ chThdSleepMilliseconds(50);
+ start_tx();
+ }
+ },
+ TRUE);
+ } else {
+ // if hop speed is not 0, just start the transmission
+ start_tx();
+ }
+ }
+ };
+
+ menu_freq_list.on_left = [this]() {
+ button_load_list.focus();
+ };
+
+ menu_freq_list.on_right = [this]() {
+ button_load_list.focus();
+ };
+
+ update_freq_list_menu_view();
+}
+
+} // namespace ui::external_app::hopper
diff --git a/firmware/application/external/hopper/ui_hopper.hpp b/firmware/application/external/hopper/ui_hopper.hpp
new file mode 100644
index 000000000..f77623b00
--- /dev/null
+++ b/firmware/application/external/hopper/ui_hopper.hpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2016 Furrtek
+ * Copyright (C) 2020 euquiq
+ * copyleft mr.r0b0t from the F society
+ *
+ * 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_language.hpp"
+#include "ui_widget.hpp"
+#include "ui_navigation.hpp"
+#include "ui_tabview.hpp"
+#include "transmitter_model.hpp"
+#include "message.hpp"
+#include "jammer.hpp"
+#include "lfsr_random.hpp"
+#include "radio_state.hpp"
+
+using namespace jammer;
+
+namespace ui::external_app::hopper {
+
+class HopperView : public View {
+ public:
+ HopperView(NavigationView& nav);
+ ~HopperView();
+
+ HopperView(const HopperView&) = delete;
+ HopperView(HopperView&&) = delete;
+ HopperView& operator=(const HopperView&) = delete;
+ HopperView& operator=(HopperView&&) = delete;
+
+ void focus() override;
+ void update_freq_list_menu_view();
+
+ std::string title() const override { return "Hopper"; };
+
+ std::vector freq_list{315000000, 433920000};
+
+ private:
+ NavigationView& nav_;
+ TxRadioState radio_state_{
+ 0 /* frequency */,
+ 3500000 /* bandwidth */,
+ 3072000 /* sampling rate */
+ };
+
+ void start_tx();
+ void on_timer();
+ void stop_tx();
+ void set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
+ void on_retune(const rf::Frequency freq, const uint32_t range);
+ void load_list();
+ void save_list();
+ void on_file_save_write(const std::string& value);
+
+ HopperChannel* hopper_channels = (HopperChannel*)shared_memory.bb_data.data;
+ bool jamming{false};
+ bool cooling{false}; // euquiq: Indicates jammer in cooldown
+ uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
+ int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
+ lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
+
+ std::string filename_buffer = "";
+
+ const Style& style_val = *Theme::getInstance()->fg_green;
+ const Style& style_cancel = *Theme::getInstance()->fg_red;
+
+ MenuView menu_freq_list{
+ {0, 0, screen_width, 8 * 16},
+ false};
+
+ NewButton button_load_list{
+ {0 * 8, 9 * 16 + 4, 4 * 8, 32},
+ {},
+ &bitmap_icon_load,
+ Color::dark_blue(),
+ /*vcenter*/ true};
+
+ NewButton button_save_list{
+ {4 * 8, 9 * 16 + 4, 4 * 8, 32},
+ {},
+ &bitmap_icon_save,
+ Color::dark_blue(),
+ /*vcenter*/ true};
+
+ NewButton button_add_freq{
+ {8 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
+ {},
+ &bitmap_icon_add,
+ Color::dark_green(),
+ /*vcenter*/ true};
+
+ NewButton button_delete_freq{
+ {12 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
+ {},
+ &bitmap_icon_trash,
+ Color::dark_red(),
+ /*vcenter*/ true};
+
+ NewButton button_clear{
+ {screen_width - 4 * 8, 9 * 16 + 4, 4 * 8, 32},
+ {},
+ &bitmap_icon_tools_wipesd,
+ Color::red(),
+ /*vcenter*/ true};
+
+ Labels labels{
+ {{2 * 8, 23 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
+ {{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
+ {{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
+ {{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
+ {{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
+ {{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
+ {{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 35 * 8}, "Gain:", Theme::getInstance()->fg_light->foreground},
+ {{11 * 8, 35 * 8}, "A:", Theme::getInstance()->fg_light->foreground}};
+
+ OptionsField options_type{
+ {7 * 8, 23 * 8},
+ 8,
+ {
+ {"Rand FSK", 0},
+ {"FM tone", 1},
+ {"CW sweep", 2},
+ {"Rand CW", 3},
+ {"Sine", 4},
+ {"Square", 5},
+ {"Sawtooth", 6},
+ {"Triangle", 7},
+ {"Chirp", 8},
+ {"Gauss", 9},
+ {"Brute", 10},
+ }};
+
+ Text text_range_number{
+ {16 * 8, 23 * 8, 2 * 8, 16},
+ "--"};
+ Text text_range_total{
+ {18 * 8, 23 * 8, 3 * 8, 16},
+ "/--"};
+
+ OptionsField options_speed{
+ {7 * 8, 25 * 8},
+ 6,
+ {{"10Hz ", 10},
+ {"100Hz ", 100},
+ {"1kHz ", 1000},
+ {"10kHz ", 10000},
+ {"100kHz", 100000}}};
+
+ OptionsField options_hop{
+ {7 * 8, 27 * 8},
+ 6,
+ {{"0ms !!", 0},
+ {"1ms ", 1},
+ {"10ms ", 10},
+ {"50ms ", 50},
+ {"100ms", 100},
+ {"1s ", 1000},
+ {"2s ", 2000},
+ {"5s ", 5000},
+ {"10s ", 10000}}};
+
+ NumberField field_timetx{
+ {7 * 8, 29 * 8},
+ 3,
+ {1, 180},
+ 1,
+ ' ',
+ };
+
+ NumberField field_timepause{
+ {8 * 8, 31 * 8},
+ 2,
+ {1, 60},
+ 1,
+ ' ',
+ };
+
+ NumberField field_jitter{
+ {8 * 8, 33 * 8},
+ 2,
+ {1, 60},
+ 1,
+ ' ',
+ };
+
+ NumberField field_gain{
+ {8 * 8, 35 * 8},
+ 2,
+ {0, 47},
+ 1,
+ ' ',
+ };
+
+ NumberField field_amp{
+ {13 * 8, 35 * 8},
+ 1,
+ {0, 1},
+ 1,
+ ' ',
+ };
+
+ Button button_transmit{
+ {148, 216, 80, 80},
+ LanguageHelper::currentMessages[LANG_START]};
+
+ MessageHandlerRegistration message_handler_retune{
+ Message::ID::Retune,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ this->on_retune(message->freq, message->range);
+ }};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->on_timer();
+ }};
+};
+
+} // namespace ui::external_app::hopper
diff --git a/firmware/application/external/jammer/ui_jammer.cpp b/firmware/application/external/jammer/ui_jammer.cpp
index c8c43f819..fc3e097a4 100644
--- a/firmware/application/external/jammer/ui_jammer.cpp
+++ b/firmware/application/external/jammer/ui_jammer.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
+ * Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
*
* This file is part of PortaPack.
*
@@ -36,7 +37,6 @@ void RangeView::focus() {
}
void RangeView::update_start(rf::Frequency f) {
- // Change everything except max
frequency_range.min = f;
button_start.set_text(to_string_short_freq(f));
@@ -48,7 +48,6 @@ void RangeView::update_start(rf::Frequency f) {
}
void RangeView::update_stop(rf::Frequency f) {
- // Change everything except min
frequency_range.max = f;
button_stop.set_text(to_string_short_freq(f));
@@ -60,7 +59,6 @@ void RangeView::update_stop(rf::Frequency f) {
}
void RangeView::update_center(rf::Frequency f) {
- // Change min/max/center, keep width
center = f;
button_center.set_text(to_string_short_freq(center));
@@ -75,7 +73,6 @@ void RangeView::update_center(rf::Frequency f) {
}
void RangeView::update_width(uint32_t w) {
- // Change min/max/width, keep center
width = w;
button_width.set_text(to_string_short_freq(width));
@@ -91,7 +88,6 @@ void RangeView::update_width(uint32_t w) {
}
void RangeView::paint(Painter&) {
- // Draw lines and arrows
Rect r;
Point p;
Coord c;
@@ -162,7 +158,7 @@ RangeView::RangeView(NavigationView& nav) {
auto load_view = nav.push();
load_view->on_frequency_loaded = [this](rf::Frequency value) {
update_center(value);
- update_width(100000); // 100kHz default jamming bandwidth when loading unique frequency
+ update_width(100000);
};
load_view->on_range_loaded = [this](rf::Frequency start, rf::Frequency stop) {
update_start(start);
@@ -193,10 +189,37 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
jammer_channels[i].enabled = true;
jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
jammer_channels[i].center = center;
- jammer_channels[i].duration = 30720 * duration;
+ jammer_channels[i].duration = duration ? 30720 * duration : 3000;
}
void JammerView::start_tx() {
+ if (update_config()) {
+ jamming = true;
+ button_transmit.set_style(&style_cancel);
+ button_transmit.set_text("STOP");
+
+ transmitter_model.set_rf_amp(field_amp.value());
+ transmitter_model.set_tx_gain(field_gain.value());
+ transmitter_model.set_baseband_bandwidth(28'000'000);
+ transmitter_model.enable();
+
+ baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+ mscounter = 0;
+ }
+}
+
+void JammerView::stop_tx() {
+ button_transmit.set_style(&style_val);
+ button_transmit.set_text("START");
+ transmitter_model.disable();
+ baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
+ jamming = false;
+ cooling = false;
+ seconds = 0;
+ mscounter = 0;
+}
+
+bool JammerView::update_config() {
uint32_t c, i = 0;
size_t num_channels;
rf::Frequency start_freq, range_bw, range_bw_sub, ch_width;
@@ -204,24 +227,19 @@ void JammerView::start_tx() {
size_t hop_value = options_hop.selected_index_value();
- // Disable all channels by default
for (c = 0; c < JAMMER_MAX_CH; c++)
jammer_channels[c].enabled = false;
- // Generate jamming channels with JAMMER_MAX_CH maximum width
- // Convert ranges min/max to center/bw
for (size_t r = 0; r < 3; r++) {
if (range_views[r]->frequency_range.enabled) {
range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min);
- // Get lower bound
if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max)
start_freq = range_views[r]->frequency_range.min;
else
start_freq = range_views[r]->frequency_range.max;
if (range_bw >= JAMMER_CH_WIDTH) {
- // Split range in multiple channels
num_channels = 0;
range_bw_sub = range_bw;
@@ -241,7 +259,6 @@ void JammerView::start_tx() {
i++;
}
} else {
- // Range fits in a single channel
if (i >= JAMMER_MAX_CH) {
out_of_ranges = true;
} else {
@@ -254,43 +271,24 @@ void JammerView::start_tx() {
if (!out_of_ranges && i) {
text_range_total.set("/" + to_string_dec_uint(i, 2));
-
- jamming = true;
- button_transmit.set_style(&style_cancel);
- button_transmit.set_text("STOP");
-
- transmitter_model.set_rf_amp(field_amp.value());
- transmitter_model.set_tx_gain(field_gain.value());
- transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
- transmitter_model.enable();
-
- baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
- mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
+ return true;
} else {
if (out_of_ranges)
nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz.");
else
nav_.display_modal("Error", "No range enabled.");
+ return false;
}
}
-void JammerView::stop_tx() {
- button_transmit.set_style(&style_val);
- button_transmit.set_text("START");
- transmitter_model.disable();
- baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
- jamming = false;
- cooling = false;
-}
-
-// called each 1/60th of second
void JammerView::on_timer() {
- if (++mscounter == 60) {
+ if (++mscounter >= 60) {
mscounter = 0;
if (jamming) {
+ int32_t timepause = field_timepause.value();
if (cooling) {
- if (++seconds >= field_timepause.value()) { // Re-start TX
- transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
+ if (timepause == 0 || ++seconds >= timepause) {
+ transmitter_model.set_baseband_bandwidth(28'000'000);
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
@@ -306,8 +304,7 @@ void JammerView::on_timer() {
seconds = 0;
}
} else {
- if (++seconds >= field_timetx.value()) // Start cooling period:
- {
+ if (timepause && ++seconds >= field_timetx.value()) {
transmitter_model.disable();
button_transmit.set_text("PAUSED");
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
@@ -327,11 +324,9 @@ void JammerView::on_timer() {
}
}
-JammerView::JammerView(
- NavigationView& nav)
+JammerView::JammerView(NavigationView& nav)
: nav_{nav} {
- Rect view_rect = {0, 3 * 8, 240, 80};
- // baseband::run_image(portapack::spi_flash::image_tag_jammer);
+ Rect view_rect = {0, 3 * 8, screen_width, 80};
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&tab_view,
@@ -355,15 +350,101 @@ JammerView::JammerView(
view_range_b.set_parent_rect(view_rect);
view_range_c.set_parent_rect(view_rect);
- options_type.set_selected_index(3); // Rand CW
- options_speed.set_selected_index(3); // 10kHz
- options_hop.set_selected_index(1); // 50ms
- button_transmit.set_style(&style_val);
+ view_range_a.check_enabled.set_value(true);
+ view_range_a.frequency_range.enabled = true;
+ view_range_a.update_center(315'000'000);
+ view_range_a.update_width(1'000'000);
+ options_type.set_selected_index(3);
+ options_speed.set_selected_index(3);
+ options_hop.set_selected_index(0);
field_timetx.set_value(30);
- field_timepause.set_value(1);
+ field_timepause.set_value(0);
+ field_jitter.set_value(0);
field_gain.set_value(transmitter_model.tx_gain());
field_amp.set_value(transmitter_model.rf_amp());
+ button_transmit.set_style(&style_val);
+
+ options_type.on_change = [this](size_t, OptionsField::value_t) {
+ if (jamming) update_config();
+ if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+ };
+
+ options_speed.on_change = [this](size_t, OptionsField::value_t) {
+ if (jamming) update_config();
+ if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+ };
+
+ options_hop.on_change = [this](size_t, OptionsField::value_t) {
+ if (jamming) update_config();
+ };
+
+ field_timetx.on_change = [this](int32_t) {
+ if (jamming) update_config();
+ };
+
+ field_timepause.on_change = [this](int32_t) {
+ if (jamming) update_config();
+ };
+
+ field_jitter.on_change = [this](int32_t) {
+ if (jamming) update_config();
+ };
+
+ field_gain.on_change = [this](int32_t v) {
+ if (jamming) transmitter_model.set_tx_gain(v);
+ };
+
+ field_amp.on_change = [this](int32_t v) {
+ if (jamming) transmitter_model.set_rf_amp(v);
+ };
+
+ for (auto range_view : range_views) {
+ range_view->check_enabled.on_select = [this](Checkbox&, bool) {
+ if (jamming) update_config();
+ };
+ range_view->button_start.on_select = [this, range_view](Button&) {
+ auto new_view = nav_.push(range_view->frequency_range.min);
+ new_view->on_changed = [this, range_view](rf::Frequency f) {
+ range_view->update_start(f);
+ if (jamming) update_config();
+ };
+ };
+ range_view->button_stop.on_select = [this, range_view](Button&) {
+ auto new_view = nav_.push(range_view->frequency_range.max);
+ new_view->on_changed = [this, range_view](rf::Frequency f) {
+ range_view->update_stop(f);
+ if (jamming) update_config();
+ };
+ };
+ range_view->button_center.on_select = [this, range_view](Button&) {
+ auto new_view = nav_.push(range_view->center);
+ new_view->on_changed = [this, range_view](rf::Frequency f) {
+ range_view->update_center(f);
+ if (jamming) update_config();
+ };
+ };
+ range_view->button_width.on_select = [this, range_view](Button&) {
+ auto new_view = nav_.push(range_view->width);
+ new_view->on_changed = [this, range_view](rf::Frequency f) {
+ range_view->update_width(f);
+ if (jamming) update_config();
+ };
+ };
+ range_view->button_load_range.on_select = [this, range_view](Button&) {
+ auto load_view = nav_.push();
+ load_view->on_frequency_loaded = [this, range_view](rf::Frequency value) {
+ range_view->update_center(value);
+ range_view->update_width(100000);
+ if (jamming) update_config();
+ };
+ load_view->on_range_loaded = [this, range_view](rf::Frequency start, rf::Frequency stop) {
+ range_view->update_start(start);
+ range_view->update_stop(stop);
+ if (jamming) update_config();
+ };
+ };
+ }
button_transmit.on_select = [this](Button&) {
if (jamming || cooling)
diff --git a/firmware/application/external/jammer/ui_jammer.hpp b/firmware/application/external/jammer/ui_jammer.hpp
index 3a34dbf2c..b338cdd47 100644
--- a/firmware/application/external/jammer/ui_jammer.hpp
+++ b/firmware/application/external/jammer/ui_jammer.hpp
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
+ * Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
*
* This file is part of PortaPack.
*
@@ -40,27 +41,12 @@ class RangeView : public View {
RangeView(NavigationView& nav);
void focus() override;
- void paint(Painter&) override;
+ void paint(Painter& painter) override;
jammer_range_t frequency_range{false, 0, 0};
-
- private:
- void update_start(rf::Frequency f);
- void update_stop(rf::Frequency f);
- void update_center(rf::Frequency f);
- void update_width(uint32_t w);
-
uint32_t width{};
rf::Frequency center{};
- const Style& style_info = *Theme::getInstance()->fg_medium;
-
- Labels labels{
- {{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
- {{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
- {{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
- {{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
-
Checkbox check_enabled{
{1 * 8, 4},
12,
@@ -73,15 +59,32 @@ class RangeView : public View {
Button button_start{
{0 * 8, 11 * 8, 11 * 8, 28},
""};
+
Button button_stop{
{19 * 8, 11 * 8, 11 * 8, 28},
""};
+
Button button_center{
{76, 4 * 15 - 4, 11 * 8, 28},
""};
+
Button button_width{
{76, 8 * 15, 11 * 8, 28},
""};
+
+ void update_start(rf::Frequency f);
+ void update_stop(rf::Frequency f);
+ void update_center(rf::Frequency f);
+ void update_width(uint32_t w);
+
+ private:
+ const Style& style_info = *Theme::getInstance()->fg_medium;
+
+ Labels labels{
+ {{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
+ {{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
+ {{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
+ {{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
};
class JammerView : public View {
@@ -109,15 +112,16 @@ class JammerView : public View {
void start_tx();
void on_timer();
void stop_tx();
+ bool update_config();
void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
void on_retune(const rf::Frequency freq, const uint32_t range);
JammerChannel* jammer_channels = (JammerChannel*)shared_memory.bb_data.data;
bool jamming{false};
- bool cooling{false}; // euquiq: Indicates jammer in cooldown
- uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
- int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
- lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
+ bool cooling{false};
+ uint16_t seconds{0};
+ int16_t mscounter{0};
+ lfsr_word_t lfsr_v{1};
const Style& style_val = *Theme::getInstance()->fg_green;
const Style& style_cancel = *Theme::getInstance()->fg_red;
@@ -139,8 +143,8 @@ class JammerView : public View {
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
- {{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
+ {{1 * 8, 31 * 8}, "Sleep:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
@@ -150,12 +154,17 @@ class JammerView : public View {
OptionsField options_type{
{7 * 8, 23 * 8},
8,
- {
- {"Rand FSK", 0},
- {"FM tone", 1},
- {"CW sweep", 2},
- {"Rand CW", 3},
- }};
+ {{"Rand FSK", 0},
+ {"FM tone", 1},
+ {"CW sweep", 2},
+ {"Noise", 3},
+ {"Sine", 4},
+ {"Square", 5},
+ {"Sawtooth", 6},
+ {"Triangle", 7},
+ {"Chirp", 8},
+ {"Gauss", 9},
+ {"Brute", 10}}};
Text text_range_number{
{16 * 8, 23 * 8, 2 * 8, 16},
@@ -176,7 +185,8 @@ class JammerView : public View {
OptionsField options_hop{
{7 * 8, 27 * 8},
5,
- {{"10ms ", 1},
+ {{"Off ", 0},
+ {"10ms ", 1},
{"50ms ", 5},
{"100ms", 10},
{"1s ", 100},
@@ -195,7 +205,7 @@ class JammerView : public View {
NumberField field_timepause{
{8 * 8, 31 * 8},
2,
- {1, 60},
+ {0, 60},
1,
' ',
};
@@ -203,7 +213,7 @@ class JammerView : public View {
NumberField field_jitter{
{8 * 8, 33 * 8},
2,
- {1, 60},
+ {0, 60},
1,
' ',
};
diff --git a/firmware/application/external/lcr/ui_lcr.cpp b/firmware/application/external/lcr/ui_lcr.cpp
index 6181dbcf6..f704f3c22 100644
--- a/firmware/application/external/lcr/ui_lcr.cpp
+++ b/firmware/application/external/lcr/ui_lcr.cpp
@@ -189,6 +189,7 @@ void LCRView::on_button_set_am(NavigationView& nav, int16_t button_id) {
nav,
litteral[button_id],
7,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this, button_id](std::string& buffer) {
texts[button_id].set(buffer);
});
@@ -257,6 +258,7 @@ LCRView::LCRView(NavigationView& nav) {
nav,
rgsb,
4,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
button_set_rgsb.set_text(buffer);
});
diff --git a/firmware/application/external/level/main.cpp b/firmware/application/external/level/main.cpp
new file mode 100644
index 000000000..d3388be53
--- /dev/null
+++ b/firmware/application/external/level/main.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_level.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::level {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::level
+
+extern "C" {
+
+__attribute__((section(".external_app.app_level.application_information"), used)) application_information_t _application_information_level = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::level::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Level",
+ /*.bitmap_data = */
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x20,
+ 0x12,
+ 0x48,
+ 0x8A,
+ 0x51,
+ 0xCA,
+ 0x53,
+ 0xCA,
+ 0x53,
+ 0x8A,
+ 0x51,
+ 0x12,
+ 0x48,
+ 0x84,
+ 0x21,
+ 0xC0,
+ 0x03,
+ 0x40,
+ 0x02,
+ 0x60,
+ 0x06,
+ 0x20,
+ 0x04,
+ 0x30,
+ 0x0C,
+ 0xF0,
+ 0x0F},
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ // this has to be the biggest baseband used by the app. Level is using AM,WFM,NFM,AMFM,SPEC and WFM is the biggest
+ /*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/apps/ui_level.cpp b/firmware/application/external/level/ui_level.cpp
similarity index 96%
rename from firmware/application/apps/ui_level.cpp
rename to firmware/application/external/level/ui_level.cpp
index e5aa5ef7f..e8034f31d 100644
--- a/firmware/application/apps/ui_level.cpp
+++ b/firmware/application/external/level/ui_level.cpp
@@ -33,7 +33,7 @@ using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
-namespace ui {
+namespace ui::external_app::level {
// Function to map the value from one range to another
int32_t LevelView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
@@ -130,9 +130,16 @@ LevelView::LevelView(NavigationView& nav)
freqman_set_modulation_option(field_mode);
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
- if (v != -1) {
- change_mode(v);
+ static freqman_index_t last_mode = WFM_MODULATION;
+ if (v > SPEC_MODULATION) {
+ if (last_mode == SPEC_MODULATION)
+ v = AM_MODULATION;
+ else
+ v = SPEC_MODULATION;
+ field_mode.set_selected_index(v);
}
+ last_mode = v;
+ change_mode(v);
};
field_mode.set_by_value(radio_mode); // Reflect the mode into the manual selector
field_bw.set_selected_index(radio_bw);
@@ -329,4 +336,4 @@ void LevelView::on_freqchg(int64_t freq) {
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
}
-} /* namespace ui */
+} // namespace ui::external_app::level
diff --git a/firmware/application/apps/ui_level.hpp b/firmware/application/external/level/ui_level.hpp
similarity index 96%
rename from firmware/application/apps/ui_level.hpp
rename to firmware/application/external/level/ui_level.hpp
index b2d03b3ef..e08a46087 100644
--- a/firmware/application/apps/ui_level.hpp
+++ b/firmware/application/external/level/ui_level.hpp
@@ -39,7 +39,7 @@
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
-namespace ui {
+namespace ui::external_app::level {
class LevelView : public View {
public:
@@ -178,12 +178,12 @@ class LevelView : public View {
RSSIGraph rssi_graph{
// 240x320 =>
- {0, 6 * 16 + 8, 240 - 5 * 8, 320 - (6 * 16)},
+ {0, 6 * 16 + 8, screen_width - 5 * 8, screen_height - (6 * 16)},
};
RSSI rssi{
// 240x320 =>
- {240 - 5 * 8, 6 * 16 + 8, 5 * 8, 320 - (6 * 16)},
+ {screen_width - 5 * 8, 6 * 16 + 8, 5 * 8, screen_height - (6 * 16)},
};
void handle_coded_squelch(const uint32_t value);
@@ -211,6 +211,6 @@ class LevelView : public View {
}};
};
-} /* namespace ui */
+} // namespace ui::external_app::level
#endif
diff --git a/firmware/application/external/lge/lge_app.cpp b/firmware/application/external/lge/lge_app.cpp
index 97e561635..82a0446c0 100644
--- a/firmware/application/external/lge/lge_app.cpp
+++ b/firmware/application/external/lge/lge_app.cpp
@@ -311,6 +311,7 @@ LGEView::LGEView(NavigationView& nav) {
nav,
nickname,
15,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
button_text.set_text(buffer);
});
diff --git a/firmware/application/external/lge/lge_app.hpp b/firmware/application/external/lge/lge_app.hpp
index fdef3807f..2e612a7ec 100644
--- a/firmware/application/external/lge/lge_app.hpp
+++ b/firmware/application/external/lge/lge_app.hpp
@@ -171,7 +171,7 @@ class LGEView : public View {
'0'};
Console console{
- {0, 18 * 8, 30 * 8, 7 * 16}};
+ {0, 18 * 8, screen_width, 7 * 16}};
TransmitterView tx_view{
16 * 16,
diff --git a/firmware/application/external/mcu_temperature/mcu_temperature.hpp b/firmware/application/external/mcu_temperature/mcu_temperature.hpp
index a8cbb617f..217ab9a25 100644
--- a/firmware/application/external/mcu_temperature/mcu_temperature.hpp
+++ b/firmware/application/external/mcu_temperature/mcu_temperature.hpp
@@ -72,16 +72,16 @@ class McuTemperatureView : public View {
private:
Text text_title{
- {76, 16, 240, 16},
+ {76, 16, screen_width, 16},
"Temperature",
};
McuTemperatureWidget temperature_widget{
- {0, 40, 240, 180},
+ {0, 40, screen_width, 180},
};
Button button_done{
- {72, 264, 96, 24},
+ {72, screen_height - 56, 96, 24},
"Done"};
};
diff --git a/firmware/application/external/metronome/ui_metronome.cpp b/firmware/application/external/metronome/ui_metronome.cpp
index 75dca3b38..3436be9cc 100644
--- a/firmware/application/external/metronome/ui_metronome.cpp
+++ b/firmware/application/external/metronome/ui_metronome.cpp
@@ -23,6 +23,7 @@
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
+#include "ui_textentry.hpp"
using namespace portapack;
@@ -42,6 +43,7 @@ MetronomeView::MetronomeView(NavigationView& nav)
&field_unaccent_beep_tune,
&field_beep_flash_duration,
&field_bpm,
+ &button_enter_tap_tempo,
&progressbar,
});
@@ -59,6 +61,14 @@ MetronomeView::MetronomeView(NavigationView& nav)
}
};
+ button_enter_tap_tempo.on_select = [this](Button&) {
+ auto tap_tempo_view = nav_.push(field_bpm.value());
+
+ tap_tempo_view->on_apply = [this](uint16_t bpm) {
+ field_bpm.set_value(bpm);
+ };
+ };
+
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
field_volume.set_value(99);
@@ -174,4 +184,116 @@ void MetronomeView::run() {
}
}
+MetronomeTapTempoView::MetronomeTapTempoView(NavigationView& nav, uint16_t bpm)
+ : nav_{nav},
+ bpm_{bpm} {
+ add_children({
+ &button_input,
+ &button_tap,
+ &button_cancel,
+ &button_apply,
+ });
+
+ bpm_when_entered_ = bpm; // save for if user cancel
+
+ // im aware that we have duplicated painter which means in this app, weo have two painter instances
+ // here is the reason why this is necessary:
+ // We need to draw the bpm big font once when enter, which would be at bad timing in constructor,
+ // cuz it happened before the view is pushed to nav, which casued it actually didn't draw
+ // which leads me have to override the paint func from father and draw inside of it.
+ //
+ // BUT I can't completely package the draw logic inside of the paint func,
+ // cuz set_dirty has flaw and cause screen flicker during the char changes, if i just package there and use set_dirty()
+ Painter painter_instance_2;
+
+ button_input.on_select = [this](Button&) {
+ input_buffer = to_string_dec_uint(bpm_);
+ text_prompt(
+ nav_,
+ input_buffer,
+ 3,
+ ENTER_KEYBOARD_MODE_DIGITS,
+ [this](std::string& buffer) {
+ if (buffer.empty()) {
+ return;
+ }
+ bpm_ = atoi(buffer.c_str());
+
+ if (on_apply) {
+ on_apply(bpm_);
+ }
+ });
+ };
+
+ button_tap.on_select = [&](Button&) {
+ on_tap(painter_instance_2);
+ };
+
+ button_apply.on_select = [this](Button&) {
+ // it's dynamically applied in tap handler
+ // the design allow user to hear changes before apply
+ nav_.pop();
+ };
+
+ button_cancel.on_select = [this](Button&) {
+ bpm_ = bpm_when_entered_;
+ if (on_apply) {
+ on_apply(bpm_);
+ }
+ nav_.pop();
+ };
+}
+
+void MetronomeTapTempoView::focus() {
+ button_tap.focus();
+}
+
+void MetronomeTapTempoView::paint(Painter& painter) {
+ View::paint(painter);
+ painter.draw_char({(0 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ / 100, 4);
+ painter.draw_char({(1 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + (bpm_ / 10) % 10, 4);
+ painter.draw_char({(2 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ % 10, 4);
+}
+
+/*
+NB: i don't really know if the cpu clock is 1000Hz AKA 1ms per tick for chTimeNow()
+ but it should be, refering to the stop watch app.
+ and also i compared with my real metronome and it's very close
+ so I assume it's 1ms per tick
+*/
+void MetronomeTapTempoView::on_tap(Painter& painter) {
+ /* ^ NB: this painter accepted from painter_instance_2*/
+ systime_t current_time = chTimeNow();
+ if (last_tap_time > 0) {
+ uint32_t interval_ms = current_time - last_tap_time;
+
+ if (interval_ms > 100) {
+ uint16_t this_time_bpm = 60000 / interval_ms;
+
+ if (this_time_bpm > 0 && this_time_bpm < 400) {
+ bpms_deque.push_back(this_time_bpm);
+ if (bpms_deque.size() > 4) { // one bar length cuz most music tempo is quarter note as 1 beat
+ bpms_deque.pop_front();
+ }
+
+ // avg
+ uint32_t sum = 0;
+ for (auto& bpm : bpms_deque) {
+ sum += bpm;
+ }
+ bpm_ = sum / bpms_deque.size();
+
+ if (on_apply) {
+ on_apply(bpm_);
+ }
+
+ painter.draw_char({(0 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ / 100, 4);
+ painter.draw_char({(1 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + (bpm_ / 10) % 10, 4);
+ painter.draw_char({(2 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ % 10, 4);
+ }
+ }
+ }
+ last_tap_time = current_time;
+}
+
} // namespace ui::external_app::metronome
\ No newline at end of file
diff --git a/firmware/application/external/metronome/ui_metronome.hpp b/firmware/application/external/metronome/ui_metronome.hpp
index d813146da..04604b573 100644
--- a/firmware/application/external/metronome/ui_metronome.hpp
+++ b/firmware/application/external/metronome/ui_metronome.hpp
@@ -27,6 +27,8 @@
#include "audio.hpp"
#include "ch.h"
+#include
+
namespace ui::external_app::metronome {
class MetronomeView : public View {
@@ -73,6 +75,11 @@ class MetronomeView : public View {
1,
' '};
+ Button button_enter_tap_tempo{
+ {(sizeof("BPM:") + 6) * 8, 1 * 16, (sizeof("Tap Tempo") + 3) * 8, 16},
+ "Tap Tempo",
+ };
+
NumberField field_rythm_unaccent_time{// e.g. 3 in 3/4 beat
{(sizeof("Rhythm:") + 1) * 8, 4 * 16},
2,
@@ -121,6 +128,45 @@ class MetronomeView : public View {
{0 * 16, 8 * 16, screen_width, screen_height - 14 * 16}};
};
+class MetronomeTapTempoView : public View {
+ public:
+ std::function on_apply{};
+
+ MetronomeTapTempoView(NavigationView& nav, uint16_t bpm);
+
+ std::string title() const override { return "Tap.T"; };
+ void focus() override;
+ void paint(Painter& painter) override;
+
+ private:
+ void on_tap(Painter& painter);
+
+ NavigationView& nav_;
+
+ uint16_t bpm_{0};
+ uint16_t bpm_when_entered_{0}; // this pass from MetronomeView and need to restore if user cancel
+ std::deque bpms_deque = {0}; // take average for recent taps to debounce
+ uint32_t last_tap_time{0};
+
+ std::string input_buffer{""}; // needed by text_prompt
+
+ Button button_input{
+ {0, 0, screen_width, 2 * 16},
+ "Input BPM"};
+
+ Button button_tap{
+ {0, 8 * 16, screen_width, 7 * 16},
+ "Tap BPM"};
+
+ Button button_cancel{
+ {1, 17 * 16, screen_width / 2 - 4, 2 * 16},
+ "Cancel"};
+
+ Button button_apply{
+ {1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
+ "Apply"};
+};
+
} // namespace ui::external_app::metronome
#endif /*__UI_METRONOME_H__*/
\ No newline at end of file
diff --git a/firmware/application/external/morse_tx/ui_morse.cpp b/firmware/application/external/morse_tx/ui_morse.cpp
index e6421ab00..054eee695 100644
--- a/firmware/application/external/morse_tx/ui_morse.cpp
+++ b/firmware/application/external/morse_tx/ui_morse.cpp
@@ -98,8 +98,33 @@ static msg_t loopthread_fn(void* arg) {
return 0;
}
+void MorseView::on_set_tone(NavigationView& nav) {
+ tone_input_buffer = to_string_dec_uint(tone);
+
+ text_prompt(nav, tone_input_buffer, 4, ENTER_KEYBOARD_MODE_DIGITS, [this](std::string& buffer) {
+ bool is_digit_only = true;
+ for (size_t i = 0; i < tone_input_buffer.size(); ++i) {
+ if (tone_input_buffer[i] < '0' || tone_input_buffer[i] > '9') {
+ is_digit_only = false;
+ break;
+ }
+ }
+ if (!buffer.empty() && is_digit_only) {
+ int new_tone = atoi(buffer.c_str());
+ if (new_tone >= 100 && new_tone <= 9999) {
+ tone = new_tone;
+ field_tone.set_value(tone);
+ update_tx_duration();
+ } else {
+ nav_.display_modal("Out of range", "Tone must be between 100 and 9999 Hz");
+ }
+ } else {
+ nav_.display_modal("Invalid input", "Please enter digits only");
+ }
+ });
+}
void MorseView::on_set_text(NavigationView& nav) {
- text_prompt(nav, buffer, 28);
+ text_prompt(nav, buffer, 28, ENTER_KEYBOARD_MODE_ALPHA);
}
void MorseView::focus() {
@@ -250,6 +275,10 @@ MorseView::MorseView(
tone = value;
};
+ field_tone.on_select = [this, &nav](NumberField&) {
+ this->on_set_tone(nav);
+ };
+
button_message.on_select = [this, &nav](Button&) {
this->on_set_text(nav);
};
diff --git a/firmware/application/external/morse_tx/ui_morse.hpp b/firmware/application/external/morse_tx/ui_morse.hpp
index 57da2cad7..fb54fcd7a 100644
--- a/firmware/application/external/morse_tx/ui_morse.hpp
+++ b/firmware/application/external/morse_tx/ui_morse.hpp
@@ -72,6 +72,7 @@ class MorseView : public View {
NavigationView& nav_;
std::string message{};
uint32_t time_units{0};
+ std::string tone_input_buffer{}; // Holds the tone value while the text prompt is open
TxRadioState radio_state_{
0 /* frequency */,
@@ -100,6 +101,7 @@ class MorseView : public View {
bool start_tx();
void update_tx_duration();
+ void on_set_tone(NavigationView& nav);
void on_set_text(NavigationView& nav);
void set_foxhunt(size_t i);
diff --git a/firmware/application/external/noaaapt_rx/main.cpp b/firmware/application/external/noaaapt_rx/main.cpp
new file mode 100644
index 000000000..0e4c8e8fb
--- /dev/null
+++ b/firmware/application/external/noaaapt_rx/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_noaaapt_rx.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::noaaapt_rx {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::noaaapt_rx
+
+extern "C" {
+
+__attribute__((section(".external_app.app_noaaapt_rx.application_information"), used)) application_information_t _application_information_noaaapt_rx = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::noaaapt_rx::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "NOAA APT",
+ /*.bitmap_data = */ {
+ 0x1C,
+ 0x80,
+ 0x3C,
+ 0x40,
+ 0x78,
+ 0x18,
+ 0xF0,
+ 0x20,
+ 0xE0,
+ 0x26,
+ 0x00,
+ 0x0F,
+ 0x80,
+ 0x0F,
+ 0xC0,
+ 0x07,
+ 0xE1,
+ 0x1B,
+ 0xC5,
+ 0x39,
+ 0x95,
+ 0x78,
+ 0x35,
+ 0xF0,
+ 0x09,
+ 0xE0,
+ 0x72,
+ 0xC0,
+ 0x04,
+ 0x00,
+ 0x78,
+ 0x00,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_noaaapt_rx */ {'P', 'N', 'O', 'A'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp
new file mode 100644
index 000000000..ff7e93ffd
--- /dev/null
+++ b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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.
+ */
+
+/*
+TODOS LATER:
+ - add load data from wav file (maybe to a separate app, not this)
+ - AGC?!?
+ - Auto doppler correction
+ - fix and enable sync detection
+ - auto start / stop bmp save on each image
+*/
+
+#include "ui_noaaapt_rx.hpp"
+
+#include "audio.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+#include "lcd_ili9341.hpp"
+
+using namespace portapack;
+using namespace modems;
+using namespace ui;
+
+namespace ui::external_app::noaaapt_rx {
+
+void NoaaAptRxView::focus() {
+ field_frequency.focus();
+}
+
+NoaaAptRxView::NoaaAptRxView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+ add_children({&rssi,
+ &field_rf_amp,
+ &field_lna,
+ &field_vga,
+ &field_volume,
+ &field_frequency,
+ &txt_status,
+ //&check_wav, // enable this or the record view, but not both. yet it has some error, says "Invalid object" a lot. so disabled it
+ //&record_view, //
+ &button_ss});
+
+ record_view.set_filename_date_frequency(true);
+ record_view.on_error = [&nav](std::string message) {
+ nav.display_modal("Error", message);
+ };
+ record_view.set_sampling_rate(12000);
+
+ field_frequency.set_step(100);
+ field_frequency.on_edit_shown = [this]() {
+ paused = true;
+ };
+ field_frequency.on_edit_hidden = [this]() {
+ paused = false;
+ };
+ audio::set_rate(audio::Rate::Hz_12000);
+ audio::output::start();
+ receiver_model.set_hidden_offset(NOAAAPT_FREQ_OFFSET);
+ receiver_model.set_baseband_bandwidth(1750000);
+ receiver_model.set_sampling_rate(3072000);
+ receiver_model.enable();
+ txt_status.set("Waiting for signal.");
+
+ button_ss.on_select = [this](Button&) {
+ if (bmp.is_loaded()) {
+ bmp.close();
+ button_ss.set_text(LanguageHelper::currentMessages[LANG_START]);
+ if (check_wav.value()) {
+ record_view.stop();
+ }
+ return;
+ }
+ if (check_wav.value()) {
+ record_view.start();
+ }
+ ensure_directory("/BMP");
+ bmp.create("/BMP/noaa_" + to_string_timestamp(rtc_time::now()) + ".bmp", NOAAAPT_PX_SIZE, 1);
+
+ button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]);
+ };
+ on_settings_changed();
+}
+
+NoaaAptRxView::~NoaaAptRxView() {
+ stopping = true;
+ receiver_model.set_hidden_offset(0);
+ bmp.close();
+ receiver_model.disable();
+ baseband::shutdown();
+ audio::output::stop();
+ record_view.stop();
+}
+
+void NoaaAptRxView::on_settings_changed() {
+ baseband::set_noaaapt_config();
+}
+
+void NoaaAptRxView::on_status(NoaaAptRxStatusDataMessage msg) {
+ (void)msg;
+ std::string tmp = "";
+ if (msg.state == 0) {
+ tmp = "Waiting for signal.";
+ } else if (msg.state == 1) {
+ tmp = "Synced.";
+ } else if (msg.state == 2) {
+ tmp = "Image arriving.";
+ }
+ txt_status.set(tmp);
+}
+
+// this stores and displays the image. keep it as simple as you can. a bit more complexity will kill the sync
+void NoaaAptRxView::on_image(NoaaAptRxImageDataMessage msg) {
+ if ((line_num) >= UI_POS_HEIGHT_REMAINING(NOAA_IMG_START_ROW)) line_num = 0; // for draw reset
+
+ for (uint16_t i = 0; i < msg.cnt; i += 1) {
+ Color pxl = {msg.image[i], msg.image[i], msg.image[i]};
+ bmp.write_next_px(pxl);
+ line_in_part++;
+ if (line_in_part == NOAAAPT_PX_SIZE) {
+ line_in_part = 0;
+ line_num++;
+ bmp.expand_y_delta(1);
+ }
+
+ uint16_t xpos = line_in_part / (NOAAAPT_PX_SIZE / 240);
+ if (xpos >= 240) xpos = 239;
+ line_buffer[xpos] = pxl;
+ if ((line_in_part == 0)) {
+ portapack::display.render_line({0, line_num + NOAA_IMG_START_ROW * 16}, 240, line_buffer);
+ }
+ }
+}
+
+} // namespace ui::external_app::noaaapt_rx
diff --git a/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp
new file mode 100644
index 000000000..a2db6b9bb
--- /dev/null
+++ b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_noaaapt_RX_H__
+#define __UI_noaaapt_RX_H__
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "ui_geomap.hpp"
+#include "ui_freq_field.hpp"
+#include "ui_record_view.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "log_file.hpp"
+#include "utility.hpp"
+#include "ui_fileman.hpp"
+#include "bmpfile.hpp"
+#include "file_path.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::noaaapt_rx {
+
+#define NOAAAPT_PX_SIZE 2080
+
+#define NOAAAPT_FREQ_OFFSET 0
+
+#define NOAA_IMG_START_ROW 4
+
+class NoaaAptRxView : public View {
+ public:
+ NoaaAptRxView(NavigationView& nav);
+ ~NoaaAptRxView();
+
+ void focus() override;
+
+ std::string title() const override { return "NOAA APT"; };
+
+ private:
+ void on_settings_changed();
+ void on_status(NoaaAptRxStatusDataMessage msg);
+ void on_image(NoaaAptRxImageDataMessage msg);
+
+ bool stopping = false;
+
+ uint16_t line_num = 0; // nth line
+ uint16_t line_in_part = 0; // got multiple parts of a line, so keep track of it
+ uint8_t delayer = 0;
+ ui::Color line_buffer[240];
+ std::filesystem::path filetohandle = "";
+
+ bool paused = false; // when freq field is shown for example, we need to pause
+
+ BMPFile bmp{};
+
+ NavigationView& nav_;
+ RxRadioState radio_state_{};
+ app_settings::SettingsManager settings_{
+ "rx_noaaapt",
+ app_settings::Mode::RX,
+ {}};
+
+ RFAmpField field_rf_amp{
+ {UI_POS_X(13), UI_POS_Y(0)}};
+ LNAGainField field_lna{
+ {UI_POS_X(15), UI_POS_Y(0)}};
+ VGAGainField field_vga{
+ {UI_POS_X(18), UI_POS_Y(0)}};
+ RSSI rssi{
+ {UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH(6), 4}};
+ AudioVolumeField field_volume{
+ {UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
+
+ RxFrequencyField field_frequency{
+ {UI_POS_X(0), UI_POS_Y(0)},
+ nav_};
+
+ RecordView record_view{
+ {UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_DEFAULT_HEIGHT},
+ u"AUD",
+ u"AUDIO",
+ RecordView::FileType::WAV,
+ 4096,
+ 4};
+
+ Checkbox check_wav{
+ {UI_POS_X(0), UI_POS_Y(2)},
+ 12,
+ "Save WAV too",
+ true};
+
+ Text txt_status{
+ {UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
+ };
+
+ Button button_ss{
+ {UI_POS_X_RIGHT(6), UI_POS_Y(1), UI_POS_WIDTH(5), UI_POS_DEFAULT_HEIGHT},
+ LanguageHelper::currentMessages[LANG_START]};
+
+ MessageHandlerRegistration message_handler_stats{
+ Message::ID::NoaaAptRxStatusData,
+ [this](const Message* const p) {
+ if (stopping || paused) return;
+ const auto message = *reinterpret_cast(p);
+ on_status(message);
+ }};
+
+ MessageHandlerRegistration message_handler_image{
+ Message::ID::NoaaAptRxImageData,
+ [this](const Message* const p) {
+ if (stopping || paused) return;
+ const auto message = *reinterpret_cast(p);
+ on_image(message);
+ }};
+};
+
+} // namespace ui::external_app::noaaapt_rx
+
+#endif /*__UI_noaaapt_RX_H__*/
diff --git a/firmware/application/external/nrf_rx/ui_nrf_rx.hpp b/firmware/application/external/nrf_rx/ui_nrf_rx.hpp
index 15b255253..0401604bd 100644
--- a/firmware/application/external/nrf_rx/ui_nrf_rx.hpp
+++ b/firmware/application/external/nrf_rx/ui_nrf_rx.hpp
@@ -77,11 +77,11 @@ class NRFRxView : public View {
nav_};
Button button_modem_setup{
- {240 - 12 * 8, 1 * 16, 96, 24},
+ {screen_width - 12 * 8, 1 * 16, 96, 24},
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
- {0, 4 * 16, 240, 240}};
+ {0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
diff --git a/firmware/application/external/ook_editor/ui_ook_editor.cpp b/firmware/application/external/ook_editor/ui_ook_editor.cpp
index a4e53cccc..e78c0aacb 100644
--- a/firmware/application/external/ook_editor/ui_ook_editor.cpp
+++ b/firmware/application/external/ook_editor/ui_ook_editor.cpp
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
+ * copyleft Elliot Alderson from F society
+ * copyleft Darlene Alderson from F society
*
* This file is part of PortaPack.
*
@@ -178,7 +180,8 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
&label_waveform,
&waveform,
&button_open,
- &button_save});
+ &button_save,
+ &button_bug_key});
// Initialize default values for controls
field_symbol_rate.set_value(100);
@@ -207,6 +210,7 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
nav,
outputFileBuffer,
64,
+ ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_save_file(buffer);
});
@@ -260,6 +264,7 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
nav,
ook_data.payload,
100,
+ ENTER_KEYBOARD_MODE_DIGITS,
[this](std::string& s) {
text_payload.set(s);
draw_waveform();
@@ -267,6 +272,16 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
});
};
+ button_bug_key.on_select = [&](Button&) {
+ auto bug_key_input_view = nav_.push(ook_data.payload);
+
+ bug_key_input_view->on_save = [this](std::string p) {
+ ook_data.payload = p;
+ text_payload.set(ook_data.payload);
+ draw_waveform();
+ };
+ };
+
// Configure button to start or stop the transmission
button_send_stop.on_select = [this](Button&) {
if (!is_transmitting) {
@@ -280,4 +295,145 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
// initial waveform drawing (should be a single line)
draw_waveform();
}
+
+/*************** bug key view ****************/
+
+OOKEditorBugKeyView::OOKEditorBugKeyView(NavigationView& nav, std::string payload)
+ : nav_{nav},
+ payload_{payload} {
+ add_children({&labels,
+ &field_primary_step,
+ &field_secondary_step,
+ &console,
+ &button_insert_high_level_long,
+ &button_insert_high_level_short,
+ &button_insert_low_level_long,
+ &button_insert_low_level_short,
+ &button_delete,
+ &button_save});
+
+ button_insert_low_level_short.on_select = [this](Button&) {
+ on_insert(InsertType::LOW_LEVEL_SHORT);
+ };
+
+ button_insert_low_level_long.on_select = [this](Button&) {
+ on_insert(InsertType::LOW_LEVEL_LONG);
+ };
+
+ button_insert_high_level_short.on_select = [this](Button&) {
+ on_insert(InsertType::HIGH_LEVEL_SHORT);
+ };
+
+ button_insert_high_level_long.on_select = [this](Button&) {
+ on_insert(InsertType::HIGH_LEVEL_LONG);
+ };
+
+ button_delete.on_select = [this](Button&) {
+ on_delete();
+ };
+
+ button_save.on_select = [this](Button&) {
+ if (on_save) on_save(build_payload());
+ nav_.pop();
+ };
+
+ auto update_step_buttons = [this](int32_t value, Button& btnLow, Button& btnHigh) {
+ std::string low_level_btn_str;
+ std::string high_level_btn_str;
+ if (value <= 14) { // the button width allow max 14 chars
+ for (int i = 0; i < value; i++) {
+ low_level_btn_str.push_back('0');
+ high_level_btn_str.push_back('1');
+ }
+ } else {
+ low_level_btn_str = to_string_dec_int(value) + " * \"0\"";
+ high_level_btn_str = to_string_dec_int(value) + " * \"1\"";
+ }
+ btnLow.set_text(" "); // set_dirty broken console. this is work around
+ btnHigh.set_text(" ");
+ btnLow.set_text(low_level_btn_str);
+ btnHigh.set_text(high_level_btn_str);
+ };
+
+ field_primary_step.on_change = [&](int32_t) {
+ update_step_buttons(field_primary_step.value(),
+ button_insert_low_level_short,
+ button_insert_high_level_short);
+ update_console();
+ };
+
+ field_secondary_step.on_change = [&](int32_t) {
+ update_step_buttons(field_secondary_step.value(),
+ button_insert_low_level_long,
+ button_insert_high_level_long);
+ update_console();
+ };
+
+ field_primary_step.set_value(1);
+ field_secondary_step.set_value(2);
+ update_step_buttons(field_primary_step.value(),
+ button_insert_low_level_short,
+ button_insert_high_level_short);
+ update_console();
+}
+
+void OOKEditorBugKeyView::on_insert(InsertType type) {
+ auto promise_length = 0;
+ std::string promose_level = "0";
+ switch (type) {
+ case InsertType::LOW_LEVEL_SHORT:
+ promise_length = field_primary_step.value();
+ promose_level = "0";
+ break;
+ case InsertType::LOW_LEVEL_LONG:
+ promise_length = field_secondary_step.value();
+ promose_level = "0";
+ break;
+ case InsertType::HIGH_LEVEL_SHORT:
+ promise_length = field_primary_step.value();
+ promose_level = "1";
+ break;
+ case InsertType::HIGH_LEVEL_LONG:
+ promise_length = field_secondary_step.value();
+ promose_level = "1";
+ break;
+ }
+
+ for (auto i = 0; i < promise_length; i++) {
+ payload_ += promose_level;
+ }
+
+ update_console();
+}
+
+void OOKEditorBugKeyView::on_delete() {
+ // I'm aware that if user inputted like: [long high][long high][short high], this will delete a pile of them
+ // but this doesnt matter because:
+ // 1. they should not do it, high or low shoudl cross each other, don't repeat
+ // 2. don't have too much RAM to trach the input trace
+ if (payload_.length() > 0) {
+ size_t len = payload_.length();
+ char last_char = payload_[len - 1];
+ size_t pos = len - 1;
+ while (pos > 0 && payload_[pos - 1] == last_char) {
+ pos--;
+ }
+ payload_.erase(pos);
+ update_console();
+ }
+}
+
+void OOKEditorBugKeyView::update_console() {
+ console.clear(true);
+ console.write(payload_);
+}
+
+std::string OOKEditorBugKeyView::build_payload() {
+ return payload_;
+}
+
+void OOKEditorBugKeyView::focus() {
+ button_save.focus();
+}
+
} // namespace ui::external_app::ook_editor
diff --git a/firmware/application/external/ook_editor/ui_ook_editor.hpp b/firmware/application/external/ook_editor/ui_ook_editor.hpp
index 343763342..ddcd15da6 100644
--- a/firmware/application/external/ook_editor/ui_ook_editor.hpp
+++ b/firmware/application/external/ook_editor/ui_ook_editor.hpp
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
+ * copyleft Elliot Alderson from F society
+ * copyleft Darlene Alderson from F society
*
* This file is part of PortaPack.
*
@@ -29,6 +31,8 @@ namespace ui::external_app::ook_editor {
#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
#define WAVEFORM_BUFFER_SIZE 550
+/*****************Editor View ******************/
+
class OOKEditorAppView : public View {
public:
void focus() override;
@@ -105,7 +109,7 @@ class OOKEditorAppView : public View {
Labels label_waveform{{{0, 188}, "Waveform:", Theme::getInstance()->fg_light->foreground}};
// Text field to display the various status message of the app
- Text text_app_status{{0, 160, 30 * 8, 16}, ""};
+ Text text_app_status{{0, 160, screen_width, 16}, ""};
// OptionsField for selectable sample rates.
OptionsField field_sample_rate{{96, 20}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}};
@@ -117,19 +121,96 @@ class OOKEditorAppView : public View {
NumberField field_repeat{{210, 40}, 3, {1, 999}, 1, '0', false};
// Text field to display the payload data.
- Text text_payload{{0 * 8, 100, 30 * 8, 16}, ""};
+ Text text_payload{{0 * 8, 100, screen_width, 16}, ""};
// Buttons for setting configurations, opening files, and starting transmission.
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]};
Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]};
+ Button button_bug_key{{0, 125 + 28 + 3, screen_width, 28}, "Bug Key"};
Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]};
// Progress bar to display transmission progress.
ProgressBar progressbar{{2 * 8, 250, 208, 16}};
// Waveform display using waveform buffer and yellow theme color.
- Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
+ Waveform waveform{{0, 208, screen_width, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
+};
+
+/******** bug key input view **********/
+
+enum InsertType {
+ LOW_LEVEL_SHORT,
+ LOW_LEVEL_LONG,
+ HIGH_LEVEL_SHORT,
+ HIGH_LEVEL_LONG
+};
+
+class OOKEditorBugKeyView : public View {
+ public:
+ std::function on_save{};
+
+ OOKEditorBugKeyView(NavigationView& nav, std::string payload);
+
+ std::string title() const override { return "Bug.K"; };
+ void focus() override;
+
+ private:
+ NavigationView& nav_;
+ std::string payload_ = "";
+ std::string path_ = "";
+ uint32_t delay_{0};
+ std::string delay_str{""}; // needed by text_prompt
+
+ void on_insert(InsertType type);
+ void on_delete();
+ void update_console();
+ std::string build_payload();
+
+ Labels labels{
+ {{0 * 8, 0 * 16}, "Primary Step", Theme::getInstance()->fg_light->foreground},
+ {{(screen_width / 2), 0 * 16}, "Secondary Step", Theme::getInstance()->fg_light->foreground}};
+
+ NumberField field_primary_step{
+ {0 * 8, 1 * 16},
+ 3,
+ {0, 550},
+ 1,
+ ' '};
+
+ NumberField field_secondary_step{
+ {(screen_width / 2), 1 * 16},
+ 3,
+ {0, 550},
+ 1,
+ ' '};
+
+ Console console{
+ {0, 3 * 16, screen_width, screen_height - 10 * 16}};
+
+ Button button_insert_low_level_long{
+ {0 * 8, 13 * 16, screen_width / 2, 2 * 16},
+ "00"};
+
+ Button button_insert_low_level_short{
+ {0 * 8, 15 * 16, screen_width / 2, 2 * 16},
+ "0"};
+
+ Button button_insert_high_level_long{
+ {(screen_width / 2), 13 * 16, screen_width / 2, 2 * 16},
+ "11"};
+
+ Button button_insert_high_level_short{
+ {(screen_width / 2), 15 * 16, screen_width / 2, 2 * 16},
+ "1"};
+
+ Button button_delete{
+ {1, 17 * 16, screen_width / 2 - 4, 2 * 16},
+ "fg_light->foreground},
+ {{0 * 8, 7 * 16}, "Stop Position:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 13 * 16}, "Encoder Type:", Theme::getInstance()->fg_light->foreground}};
+
Button button_startstop{
- {0, 3 * 16, 96, 24},
+ {8, screen_height - 48 - 16, screen_width - 2 * 8, 48},
LanguageHelper::currentMessages[LANG_START]};
+ Button button_input_start_position{
+ {8, 4 * 16, screen_width - 2 * 8, 24},
+ "Input Start Pos"};
+
+ Button button_input_stop_position{
+ {8, 9 * 16, screen_width - 2 * 8, 24},
+ "Input Stop Pos"};
+
NumberField field_start{
- {0 * 8, 1 * 16},
+ {0 * 8, 3 * 16},
8,
{0, 2500},
1,
@@ -65,15 +79,16 @@ class OOKBruteView : public View {
true};
NumberField field_stop{
- {11 * 8, 1 * 16},
+ {0, 8 * 16},
9,
{0, 2500},
1,
' ',
true};
+ // NB: when add new encoder here you should also change the char count limit for input range buttons by it's digits in DEC
OptionsField options_atkmode{
- {0 * 8, 2 * 16},
+ {0, 14 * 16},
12,
{{"Came12", 0},
{"Came24", 1},
@@ -86,6 +101,8 @@ class OOKBruteView : public View {
uint32_t counter = 0; // for packet change
+ std::string text_input_buffer{}; // this is needed by the text_prompt func
+
void start();
void stop();
diff --git a/firmware/application/external/playlist_editor/main.cpp b/firmware/application/external/playlist_editor/main.cpp
new file mode 100644
index 000000000..3ffe23c5b
--- /dev/null
+++ b/firmware/application/external/playlist_editor/main.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_playlist_editor.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::playlist_editor {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::playlist_editor
+
+extern "C" {
+
+__attribute__((section(".external_app.app_playlist_editor.application_information"), used)) application_information_t _application_information_playlist_editor = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::playlist_editor::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "PlaylistEdit",
+ /*.bitmap_data = */ {
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x0F,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x01,
+ 0x80,
+ 0x01,
+ 0xC3,
+ 0x00,
+ 0xE0,
+ 0xFF,
+ 0xEF,
+ 0xFF,
+ 0xC0,
+ 0x00,
+ 0x83,
+ 0x01,
+ 0x00,
+ 0x01,
+ 0x03,
+ 0x00,
+ 0x00,
+ 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/playlist_editor/ui_playlist_editor.cpp b/firmware/application/external/playlist_editor/ui_playlist_editor.cpp
new file mode 100644
index 000000000..6b9567a56
--- /dev/null
+++ b/firmware/application/external/playlist_editor/ui_playlist_editor.cpp
@@ -0,0 +1,369 @@
+/*
+ * copyleft Elliot Alderson from F society
+ * copyleft Darlene Alderson from F society
+ *
+ * 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_playlist_editor.hpp"
+#include "ui_navigation.hpp"
+#include "ui_external_items_menu_loader.hpp"
+
+#include "file.hpp"
+#include "ui_fileman.hpp"
+#include "file_path.hpp"
+#include "string_format.hpp"
+
+namespace fs = std::filesystem;
+
+#include "string_format.hpp"
+
+#include "file_reader.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::playlist_editor {
+
+/*********menu**********/
+PlaylistEditorView::PlaylistEditorView(NavigationView& nav)
+ : nav_{nav} {
+ portapack::async_tx_enabled = true;
+ add_children({&labels,
+ &button_new,
+ &text_current_ppl_file,
+ &menu_view,
+ &text_hint,
+ &button_open_playlist,
+ &button_edit,
+ &button_insert,
+ &button_save_playlist});
+
+ menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
+
+ menu_view.on_highlight = [this]() {
+ text_hint.set("Edit:" +
+ playlist[menu_view.highlighted_index()].substr(playlist[menu_view.highlighted_index()].find_last_of('/') + 1,
+ playlist[menu_view.highlighted_index()].find(',') -
+ playlist[menu_view.highlighted_index()].find_last_of('/') - 1));
+ };
+
+ button_new.on_select = [this](Button&) {
+ if (on_create_ppl()) {
+ swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
+ refresh_interface();
+ }
+ };
+
+ button_open_playlist.on_select = [this](Button&) {
+ open_file();
+ };
+
+ button_edit.on_select = [this](Button&) {
+ on_edit_item();
+ };
+
+ button_insert.on_select = [this](Button&) {
+ on_insert_item();
+ };
+
+ button_save_playlist.on_select = [this](Button&) {
+ save_ppl();
+ };
+
+ swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON);
+}
+
+void PlaylistEditorView::focus() {
+ menu_view.focus();
+}
+
+void PlaylistEditorView::open_file() {
+ auto open_view = nav_.push(".PPL");
+ open_view->push_dir(playlist_dir);
+ open_view->on_changed = [this](fs::path new_file_path) {
+ current_ppl_path = new_file_path;
+ on_file_changed(new_file_path);
+ };
+}
+
+void PlaylistEditorView::swap_opened_file_or_new_button(DisplayFilenameOrNewButton d) {
+ if (d == DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON) {
+ button_new.hidden(false);
+ text_current_ppl_file.hidden(true);
+ } else {
+ button_new.hidden(true);
+ text_current_ppl_file.hidden(false);
+ text_current_ppl_file.set(current_ppl_name_buffer);
+ }
+ refresh_interface();
+}
+
+/*
+NB: same name would became as "open file"
+*/
+bool PlaylistEditorView::on_create_ppl() {
+ bool success = false;
+ text_prompt(
+ nav_,
+ current_ppl_name_buffer,
+ 100,
+ ENTER_KEYBOARD_MODE_ALPHA,
+ [&](std::string& s) {
+ current_ppl_name_buffer = s;
+
+ success = true;
+ current_ppl_name_buffer += ".PPL";
+ current_ppl_path = playlist_dir / std::filesystem::path(current_ppl_name_buffer);
+
+ File f;
+ f.open(current_ppl_path, true, true); // prob safer here as standalone obj as read only and then open again in process func
+ f.close();
+ on_file_changed(current_ppl_path);
+ });
+
+ return success;
+}
+
+void PlaylistEditorView::on_file_changed(const fs::path& new_file_path) {
+ File playlist_file;
+ auto error = playlist_file.open(new_file_path.string());
+
+ if (error) return;
+
+ menu_view.clear();
+ auto reader = FileLineReader(playlist_file);
+
+ for (const auto& line : reader) {
+ playlist.push_back(line);
+ }
+
+ for (auto& line : playlist) {
+ // remove empty lines
+ if (line == "\n" || line == "\r\n" || line == "\r") {
+ playlist.erase(std::remove(playlist.begin(), playlist.end(), line), playlist.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);
+ }
+ }
+ text_hint.set("Highlight an entry");
+
+ text_current_ppl_file.set(new_file_path.string());
+
+ ever_opened = true;
+
+ swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
+
+ refresh_menu_view();
+}
+
+void PlaylistEditorView::refresh_menu_view() {
+ menu_view.clear();
+
+ for (const auto& line : playlist) {
+ if (line.length() == 0 || line[0] == '#') {
+ menu_view.add_item({line,
+ ui::Color::grey(),
+ &bitmap_icon_notepad,
+ [this](KeyEvent) {
+ button_insert.focus();
+ }});
+ } else {
+ const auto filename = line.substr(line.find_last_of('/') + 1, line.find(',') - line.find_last_of('/') - 1);
+ menu_view.add_item({filename,
+ ui::Color::white(),
+ &bitmap_icon_cwgen,
+ [this](KeyEvent) {
+ button_edit.focus();
+ }});
+ }
+ }
+}
+
+void PlaylistEditorView::on_edit_item() {
+ if (!ever_opened || playlist.empty()) {
+ nav_.display_modal("Err", "No entry");
+ return;
+ }
+ auto edit_view = nav_.push(
+ playlist[menu_view.highlighted_index()]);
+
+ edit_view->set_on_delete([this]() {
+ playlist.erase(playlist.begin() + menu_view.highlighted_index());
+ refresh_interface();
+ });
+
+ edit_view->on_save = [this](std::string new_item) {
+ playlist[menu_view.highlighted_index()] = new_item;
+ refresh_interface();
+ };
+}
+
+void PlaylistEditorView::on_insert_item() {
+ // if (current_ppl_path.empty() || current_ppl_path.string().find_first_not_of(" \t\n\r") == std::string::npos) {
+ if (!ever_opened) { // TODO: this is a workaround because the above line is not working and I took one hour and didn't find the issue
+ nav_.display_modal("Err", "No playlist file loaded");
+ return;
+ }
+
+ auto edit_view = nav_.push(
+ "");
+
+ edit_view->on_save = [&](std::string new_item) {
+ if (playlist.empty()) {
+ playlist.push_back(new_item);
+ } else {
+ playlist.insert(playlist.begin() + menu_view.highlighted_index() + 1, new_item);
+ }
+ refresh_interface();
+ };
+}
+
+void PlaylistEditorView::refresh_interface() {
+ const auto previous_index = menu_view.highlighted_index();
+ refresh_menu_view();
+ set_dirty();
+ menu_view.set_highlighted(previous_index);
+}
+
+void PlaylistEditorView::save_ppl() {
+ if (current_ppl_path.empty()) {
+ nav_.display_modal("Err", "No playlist file loaded");
+ return;
+ } else if (playlist.empty()) {
+ nav_.display_modal("Err", "List is empty");
+ return;
+ }
+
+ File playlist_file;
+ auto error = playlist_file.open(current_ppl_path.string(), false, false);
+
+ if (error) {
+ nav_.display_modal("Err", "open err");
+ return;
+ }
+
+ // clear file
+ playlist_file.seek(0);
+ playlist_file.truncate();
+
+ // write new data
+ for (const auto& entry : playlist) {
+ playlist_file.write_line(entry);
+ }
+
+ nav_.display_modal("Save", "Saved playlist\n" + current_ppl_path.string());
+}
+
+/*********edit**********/
+
+PlaylistItemEditView::PlaylistItemEditView(
+ NavigationView& nav,
+ std::string item)
+ : nav_{nav},
+ original_item_{item} {
+ add_children({&labels,
+ &field_path,
+ &field_delay,
+ &button_browse,
+ &button_input_delay,
+ &button_delete,
+ &button_save});
+
+ button_browse.on_select = [this, &nav](Button&) {
+ auto open_view = nav.push(".C16");
+ open_view->push_dir(captures_dir);
+ open_view->on_changed = [this](fs::path path) {
+ field_path.set_text(path.string());
+ path_ = path.string();
+ };
+ field_delay.on_change = [&](auto) {
+ delay_ = field_delay.value();
+ };
+ };
+
+ button_input_delay.on_select = [this](Button&) {
+ delay_str = to_string_dec_uint(delay_);
+ if (delay_str == "0") {
+ delay_str = "";
+ }
+ text_prompt(
+ nav_,
+ delay_str,
+ 100,
+ ENTER_KEYBOARD_MODE_ALPHA,
+ [&](std::string& s) {
+ delay_ = atoi(s.c_str());
+ field_delay.set_value(delay_);
+ refresh_ui();
+ });
+ };
+
+ button_delete.on_select = [this](Button&) {
+ if (on_delete) on_delete();
+ nav_.pop();
+ };
+
+ button_save.on_select = [&](Button&) {
+ if (path_.empty()) {
+ nav_.display_modal("Err", "Select a file\n or press back to cancel");
+ return;
+ }
+ if (on_save) on_save(build_item());
+ nav_.pop();
+ };
+
+ if (!on_delete) {
+ button_delete.hidden(true);
+ }
+
+ parse_item(item);
+ refresh_ui();
+}
+
+void PlaylistItemEditView::focus() {
+ button_save.focus();
+}
+
+void PlaylistItemEditView::refresh_ui() {
+ field_path.set_text(path_);
+ field_delay.set_value(delay_);
+}
+
+void PlaylistItemEditView::parse_item(std::string item) {
+ // Parse format: path,delay
+ if (item.empty()) {
+ return;
+ }
+ auto parts = split_string(item, ',');
+ if (parts.size() >= 1) {
+ path_ = std::string{parts[0]};
+ }
+ if (parts.size() >= 2) {
+ delay_ = atoi(std::string{parts[1]}.c_str());
+ }
+}
+
+std::string PlaylistItemEditView::build_item() const {
+ const auto v = path_ + "," + to_string_dec_uint(field_delay.value());
+ return path_ + "," + to_string_dec_uint(field_delay.value());
+}
+
+} // namespace ui::external_app::playlist_editor
\ No newline at end of file
diff --git a/firmware/application/external/playlist_editor/ui_playlist_editor.hpp b/firmware/application/external/playlist_editor/ui_playlist_editor.hpp
new file mode 100644
index 000000000..c8d70f532
--- /dev/null
+++ b/firmware/application/external/playlist_editor/ui_playlist_editor.hpp
@@ -0,0 +1,159 @@
+/*
+ * copyleft Elliot Alderson from F society
+ * copyleft Darlene Alderson from F society
+ *
+ * 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_PLAYLIST_EDITOR_H__
+#define __UI_PLAYLIST_EDITOR_H__
+
+#include "ui_navigation.hpp"
+namespace fs = std::filesystem;
+
+namespace ui::external_app::playlist_editor {
+
+enum DisplayFilenameOrNewButton {
+ DISPLAY_FILENAME,
+ DISPLAY_NEW_BUTTON
+};
+
+class PlaylistEditorView : public View {
+ public:
+ PlaylistEditorView(NavigationView& nav);
+ std::string title() const override { return "PPL Edit"; };
+
+ private:
+ NavigationView& nav_;
+
+ void focus() override;
+
+ std::vector playlist = {};
+ fs::path current_ppl_path = "";
+ std::string current_ppl_name_buffer = ""; // this is because text_prompt needs it. TODO: this is so annoying, shoudl refactor that func
+ bool ever_opened = false;
+
+ Labels labels{
+ {{0 * 8, 0 * 16}, "PPL file:", Theme::getInstance()->fg_light->foreground}};
+
+ Button button_new{
+ {(sizeof("PPL file:") + 1) * 8, 0 * 16, 8 * 5, 16},
+ "New"};
+
+ Text text_current_ppl_file{
+ {sizeof("PPL file:") * 8, 0 * 16, screen_width - (int)sizeof("PPL file:") * 8, 16},
+ ""};
+
+ MenuView menu_view{};
+
+ Text text_hint{
+ {0, 27 * 8, screen_width, 16},
+ "Open a PPL file"};
+
+ Text text_ppl_name{
+ {0, 27 * 8, screen_width, 16},
+ "Highlight an app"};
+
+ Button button_open_playlist{
+ {0, 29 * 8, screen_width / 2 - 1, 32},
+ "Open PPL"};
+
+ Button button_edit{
+ {screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
+ "Edit Item"};
+
+ Button button_insert{
+ {0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
+ "Ins. After"};
+
+ Button button_save_playlist{
+ {screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
+ "Save PPL"};
+
+ void open_file();
+ void swap_opened_file_or_new_button(DisplayFilenameOrNewButton d);
+ void on_file_changed(const fs::path& path);
+ bool on_create_ppl();
+ void refresh_interface();
+ void on_edit_item();
+ void on_insert_item();
+ void refresh_menu_view();
+ void save_ppl();
+};
+
+class PlaylistItemEditView : public View {
+ public:
+ std::function on_save{};
+ std::function on_delete{};
+
+ PlaylistItemEditView(NavigationView& nav, std::string item);
+
+ std::string title() const override { return "Edit Item"; };
+ void focus() override;
+
+ void set_on_delete(std::function callback) {
+ on_delete = callback;
+ button_delete.hidden(false);
+ }
+
+ private:
+ NavigationView& nav_;
+ std::string original_item_ = "";
+ std::string path_ = "";
+ uint32_t delay_{0};
+ std::string delay_str{""}; // needed by text_prompt
+
+ void refresh_ui();
+ void parse_item(std::string item);
+ std::string build_item() const;
+
+ Labels labels{
+ {{0 * 8, 1 * 16}, "Path:", Theme::getInstance()->fg_light->foreground},
+ {{2 * 8, 5 * 16}, "Delay(ms):", Theme::getInstance()->fg_light->foreground}};
+
+ TextField field_path{
+ {0, 2 * 16, screen_width, 16},
+ "empty"};
+
+ NumberField field_delay{
+ {11 * 8, 5 * 16},
+ 5,
+ {0, 99999},
+ 10,
+ ' '};
+
+ Button button_browse{
+ {2 * 8, 8 * 16, 8 * 8, 3 * 16},
+ "Browse"};
+
+ Button button_input_delay{
+ {12 * 8, 8 * 16, sizeof("Input Delay") * 8, 3 * 16},
+ "Input Delay"};
+
+ Button button_delete{
+ {1, 17 * 16, screen_width / 2 - 4, 2 * 16},
+ "Delete"};
+
+ Button button_save{
+ {1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
+ "Save"};
+};
+
+} // namespace ui::external_app::playlist_editor
+
+#endif // __UI_PLAYLIST_EDITOR_H__
\ No newline at end of file
diff --git a/firmware/application/external/protoview/ui_protoview.hpp b/firmware/application/external/protoview/ui_protoview.hpp
index b356bcf8c..ff78fa648 100644
--- a/firmware/application/external/protoview/ui_protoview.hpp
+++ b/firmware/application/external/protoview/ui_protoview.hpp
@@ -60,29 +60,29 @@ class ProtoView : public View {
"rx_protoview", app_settings::Mode::RX};
RFAmpField field_rf_amp{
- {13 * 8, 0 * 16}};
+ {UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
- {15 * 8, 0 * 16}};
+ {UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
- {18 * 8, 0 * 16}};
+ {UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
- {21 * 8, 0, 6 * 8, 4}};
+ {UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH(6), 4}};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
RxFrequencyField field_frequency{
- {0 * 8, 0 * 16},
+ {UI_POS_X(0), UI_POS_Y(0)},
nav_};
// need to seperate because label shift need to hide independently
Labels label_zoom{
- {{0 * 8, 1 * 16}, "Zoom: ", Theme::getInstance()->fg_light->foreground}};
+ {{UI_POS_X(0), UI_POS_Y(1)}, "Zoom: ", Theme::getInstance()->fg_light->foreground}};
Labels label_shift{
- {{0 * 8, 2 * 16}, "Shift: ", Theme::getInstance()->fg_light->foreground}};
+ {{UI_POS_X(0), UI_POS_Y(2)}, "Shift: ", Theme::getInstance()->fg_light->foreground}};
OptionsField options_zoom{
- {7 * 8, 1 * 16},
+ {UI_POS_X(7), UI_POS_Y(1)},
4,
{{"1", 1},
{"2", 2},
@@ -96,22 +96,22 @@ class ProtoView : public View {
{"1000", 1000}}};
NumberField number_shift{
- {7 * 8, 2 * 16},
+ {UI_POS_X(7), UI_POS_Y(2)},
5,
{-MAXSIGNALBUFFER, MAXSIGNALBUFFER},
1,
' '};
Button button_reset{
- {screen_width - 12 * 8, 1 * 16, 96, 24},
+ {UI_POS_X_RIGHT(12), UI_POS_Y(1), UI_POS_WIDTH(12), UI_POS_HEIGHT(1.5)},
LanguageHelper::currentMessages[LANG_RESET]};
Button button_pause{
- {screen_width - 12 * 8, 1 * 16 + 24, 96, 24},
+ {UI_POS_X_RIGHT(12), UI_POS_Y(2.5), UI_POS_WIDTH(12), UI_POS_HEIGHT(1.5)},
LanguageHelper::currentMessages[LANG_PAUSE]};
Waveform waveform{
- {0, 8 * 8, 240, 50},
+ {UI_POS_X(0), UI_POS_Y(4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
waveform_buffer,
0,
0,
@@ -119,7 +119,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform2{
- {0, 8 * 8 + 55, 240, 50},
+ {UI_POS_X(0), UI_POS_Y(4) + (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF],
0,
0,
@@ -127,7 +127,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform3{
- {0, 8 * 8 + 110, 240, 50},
+ {UI_POS_X(0), UI_POS_Y(4) + 2 * (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF * 2],
0,
0,
@@ -135,7 +135,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform4{
- {0, 8 * 8 + 165, 240, 50},
+ {UI_POS_X(0), UI_POS_Y(4) + 3 * (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF * 3],
0,
0,
diff --git a/firmware/application/external/random_password/main.cpp b/firmware/application/external/random_password/main.cpp
index f92851e10..dbd70046f 100644
--- a/firmware/application/external/random_password/main.cpp
+++ b/firmware/application/external/random_password/main.cpp
@@ -38,7 +38,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
- /*.app_name = */ "Random passwd",
+ /*.app_name = */ "Rand Pwd",
/*.bitmap_data = */ {
0xC0,
0x03,
diff --git a/firmware/application/external/random_password/ui_random_password.hpp b/firmware/application/external/random_password/ui_random_password.hpp
index 24ebb63e5..8586a9733 100644
--- a/firmware/application/external/random_password/ui_random_password.hpp
+++ b/firmware/application/external/random_password/ui_random_password.hpp
@@ -203,7 +203,7 @@ class RandomPasswordView : public View {
' '};
OptionsField field_method{
- {(screen_width / 2) + (sizeof("method:") - 1) * 8, 7 * 16 - 2},
+ {(screen_width / 2) + (int)(sizeof("method:") - 1) * 8, 7 * 16 - 2},
sizeof("R+L+R+H"),
{{"R+L+R", Method::RADIO_LCG_ROLL},
{"R+L+R+H", Method::RADIO_LCG_ROLL_HASH}}};
diff --git a/firmware/application/external/remote/main.cpp b/firmware/application/external/remote/main.cpp
index 358452a4b..8c0ae1592 100644
--- a/firmware/application/external/remote/main.cpp
+++ b/firmware/application/external/remote/main.cpp
@@ -75,7 +75,7 @@ __attribute__((section(".external_app.app_remote.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::HOME,
- /*.desired_menu_position = */ 4,
+ /*.desired_menu_position = */ 6,
/*.m4_app_tag = portapack::spi_flash::image_tag_replay */ {'P', 'R', 'E', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/remote/ui_remote.cpp b/firmware/application/external/remote/ui_remote.cpp
index b4f7f6504..e24dd3646 100644
--- a/firmware/application/external/remote/ui_remote.cpp
+++ b/firmware/application/external/remote/ui_remote.cpp
@@ -330,7 +330,7 @@ RemoteAppView::RemoteAppView(
field_title.on_select = [this, &nav](TextField&) {
temp_buffer_ = model_.name;
- text_prompt(nav_, temp_buffer_, text_edit_max, [this](std::string& new_name) {
+ text_prompt(nav_, temp_buffer_, text_edit_max, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
model_.name = new_name;
refresh_ui();
set_needs_save();
@@ -339,7 +339,7 @@ RemoteAppView::RemoteAppView(
field_filename.on_select = [this, &nav](TextField&) {
temp_buffer_ = remote_path_.stem().string();
- text_prompt(nav_, temp_buffer_, text_edit_max, [this](std::string& new_name) {
+ text_prompt(nav_, temp_buffer_, text_edit_max, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
rename_remote(new_name);
refresh_ui();
});
diff --git a/firmware/application/external/remote/ui_remote.hpp b/firmware/application/external/remote/ui_remote.hpp
index e61834f44..b8a7ec251 100644
--- a/firmware/application/external/remote/ui_remote.hpp
+++ b/firmware/application/external/remote/ui_remote.hpp
@@ -292,12 +292,12 @@ class RemoteAppView : public View {
bool is_sending() const { return replay_thread_ != nullptr; }
void show_error(const std::string& msg) const;
- static constexpr Dim button_rows = 4;
- static constexpr Dim button_cols = 3;
- static constexpr uint8_t max_buttons = button_rows * button_cols;
- static constexpr Dim button_area_height = 200;
- static constexpr Dim button_width = screen_width / button_cols;
- static constexpr Dim button_height = button_area_height / button_rows;
+ Dim button_rows = 4;
+ Dim button_cols = 3;
+ uint8_t max_buttons = button_rows * button_cols;
+ Dim button_area_height = 200;
+ Dim button_width = screen_width / button_cols;
+ Dim button_height = button_area_height / button_rows;
// This value is mysterious... why?
static constexpr uint32_t baseband_bandwidth = 2'500'000;
@@ -328,7 +328,7 @@ class RemoteAppView : public View {
bool ready_signal_{}; // Used to signal ReplayThread ready.
TextField field_title{
- {0 * 8, 0 * 16 + 2, 30 * 8, 1 * 16},
+ {0 * 8, 0 * 16 + 2, screen_width, 1 * 16},
{}};
TransmitterView2 tx_view{
diff --git a/firmware/application/external/scanner/main.cpp b/firmware/application/external/scanner/main.cpp
new file mode 100644
index 000000000..0fcc9e5c5
--- /dev/null
+++ b/firmware/application/external/scanner/main.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_scanner.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::scanner {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::scanner
+
+extern "C" {
+
+__attribute__((section(".external_app.app_scanner.application_information"), used)) application_information_t _application_information_scanner = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::scanner::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Scanner",
+ /*.bitmap_data = */ {0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x03, 0x01, 0x80, 0x01, 0xC3, 0x00, 0xE0, 0xFF, 0xEF, 0xFF, 0xC0, 0x00, 0x83, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00},
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ // this has to be the biggest baseband used by the app. Scanner is using AM,WFM,NFM and WFM is the biggest
+ /*.m4_app_tag = portapack::spi_flash::image_tag_scanner */ {'P', 'W', 'F', 'M'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/apps/ui_scanner.cpp b/firmware/application/external/scanner/ui_scanner.cpp
similarity index 99%
rename from firmware/application/apps/ui_scanner.cpp
rename to firmware/application/external/scanner/ui_scanner.cpp
index edd0d6121..8d87b6c39 100644
--- a/firmware/application/apps/ui_scanner.cpp
+++ b/firmware/application/external/scanner/ui_scanner.cpp
@@ -28,10 +28,11 @@
#include "ui_freqman.hpp"
#include "file_path.hpp"
+using namespace ui;
using namespace portapack;
namespace fs = std::filesystem;
-namespace ui {
+namespace ui::external_app::scanner {
ScannerThread::ScannerThread(std::vector frequency_list)
: frequency_list_{std::move(frequency_list)} {
@@ -451,13 +452,14 @@ ScannerView::ScannerView(
// Mode field was changed (AM/NFM/WFM)
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
- static freqman_index_t last_mode = AM_MODULATION;
+ // initialize to a value under SPEC
+ static freqman_index_t last_mode = WFM_MODULATION;
// unsupported SPEC mode fix
- if (v == SPEC_MODULATION) {
- if (last_mode == AM_MODULATION)
- v = WFM_MODULATION;
- else
+ if (v >= SPEC_MODULATION) {
+ if (last_mode == WFM_MODULATION)
v = AM_MODULATION;
+ else
+ v = WFM_MODULATION;
field_mode.set_selected_index(v);
}
last_mode = v;
@@ -765,5 +767,4 @@ void ScannerView::restart_scan() {
start_scan_thread(); // RESTART SCANNER THREAD in selected mode
}
-
-} /* namespace ui */
+} // namespace ui::external_app::scanner
diff --git a/firmware/application/apps/ui_scanner.hpp b/firmware/application/external/scanner/ui_scanner.hpp
similarity index 97%
rename from firmware/application/apps/ui_scanner.hpp
rename to firmware/application/external/scanner/ui_scanner.hpp
index 4ba52bc45..bb5821907 100644
--- a/firmware/application/apps/ui_scanner.hpp
+++ b/firmware/application/external/scanner/ui_scanner.hpp
@@ -35,12 +35,14 @@
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
+using namespace ui;
+
+namespace ui::external_app::scanner {
+
#define SCANNER_SLEEP_MS 50 // ms that Scanner Thread sleeps per loop
#define STATISTICS_UPDATES_PER_SEC 10
#define MAX_FREQ_LOCK 10 // # of 50ms cycles scanner locks into freq when signal detected, to verify signal is not spurious
-namespace ui {
-
// TODO: There is too much duplicated data in these classes.
// ScannerThread should just use more from the View.
// Or perhaps ScannerThread should just be in the View.
@@ -215,7 +217,7 @@ class ScannerView : public View {
NumberField field_lock_wait{
// Signal-Lost wait timer - time to wait before moving on after losing signal lock
- {28 * 8, 1 * 16},
+ {screen_width - 2 * 8, 1 * 16},
2,
{0, 99},
1,
@@ -236,7 +238,7 @@ class ScannerView : public View {
};
Text text_current_desc{
- {0, 4 * 16, 240 - 6 * 8, 16},
+ {0, 4 * 16, screen_width - 6 * 8, 16},
};
BigFrequency big_display{
@@ -314,5 +316,4 @@ class ScannerView : public View {
this->on_statistics_update(static_cast(p)->statistics);
}};
};
-
-} /* namespace ui */
+} // namespace ui::external_app::scanner
diff --git a/firmware/application/external/sd_wipe/main.cpp b/firmware/application/external/sd_wipe/main.cpp
new file mode 100644
index 000000000..09a304212
--- /dev/null
+++ b/firmware/application/external/sd_wipe/main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_sd_wipe.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::sd_wipe {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::sd_wipe
+
+extern "C" {
+
+__attribute__((section(".external_app.app_sd_wipe.application_information"), used)) application_information_t _application_information_sd_wipe = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::sd_wipe::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Wipe SD card",
+ /*.bitmap_data = */ {0xF0, 0x3F, 0x58, 0x35, 0x5C, 0x35, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0x3C, 0x1C, 0xBC, 0xC9, 0xBC, 0xE3, 0x2C, 0x77, 0x5C, 0x3E, 0xAC, 0x1C, 0x5C, 0x3E, 0x2C, 0x77, 0x9C, 0xE3, 0xAC, 0xC1},
+ /*.icon_color = */ ui::Color::red().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
+};
+}
diff --git a/firmware/application/apps/ui_sd_wipe.cpp b/firmware/application/external/sd_wipe/ui_sd_wipe.cpp
similarity index 94%
rename from firmware/application/apps/ui_sd_wipe.cpp
rename to firmware/application/external/sd_wipe/ui_sd_wipe.cpp
index c035691bc..a482e784a 100644
--- a/firmware/application/apps/ui_sd_wipe.cpp
+++ b/firmware/application/external/sd_wipe/ui_sd_wipe.cpp
@@ -22,7 +22,9 @@
#include "ui_sd_wipe.hpp"
-namespace ui {
+using namespace ui;
+
+namespace ui::external_app::sd_wipe {
Thread* WipeSDView::thread{nullptr};
@@ -63,4 +65,4 @@ void WipeSDView::focus() {
}
}
-} /* namespace ui */
+} // namespace ui::external_app::sd_wipe
diff --git a/firmware/application/apps/ui_sd_wipe.hpp b/firmware/application/external/sd_wipe/ui_sd_wipe.hpp
similarity index 94%
rename from firmware/application/apps/ui_sd_wipe.hpp
rename to firmware/application/external/sd_wipe/ui_sd_wipe.hpp
index d3b873470..4ffc88b8d 100644
--- a/firmware/application/apps/ui_sd_wipe.hpp
+++ b/firmware/application/external/sd_wipe/ui_sd_wipe.hpp
@@ -30,7 +30,9 @@
#include
-namespace ui {
+using namespace ui;
+
+namespace ui::external_app::sd_wipe {
class WipeSDView : public View {
public:
@@ -83,10 +85,10 @@ class WipeSDView : public View {
{2 * 8, 19 * 8, 26 * 8, 24}};
Button dummy{
- {240, 0, 0, 0},
+ {screen_width, 0, 0, 0},
""};
};
-} /* namespace ui */
+} // namespace ui::external_app::sd_wipe
#endif /*__UI_SD_WIPE_H__*/
diff --git a/firmware/application/external/shoppingcart_lock/main.cpp b/firmware/application/external/shoppingcart_lock/main.cpp
index 7d7a6292c..300c4b217 100644
--- a/firmware/application/external/shoppingcart_lock/main.cpp
+++ b/firmware/application/external/shoppingcart_lock/main.cpp
@@ -1,6 +1,8 @@
+// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
+// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
+// If you can read this, you're a nerd. :P
+// Come join us at https://discord.gg/thepiratesreborn
-// RocketGod's Shopping Cart Lock app
-// https://betaskynet.com
#include "ui.hpp"
#include "shoppingcart_lock.hpp"
#include "ui_navigation.hpp"
@@ -60,7 +62,7 @@ __attribute__((section(".external_app.app_shoppingcart_lock.application_informat
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
- /*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'T', 'X'},
- /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+ /*.m4_app_tag = portapack::spi_flash::image_tag_audio_tx */ {'P', 'A', 'T', 'X'},
+ /*.m4_app_offset = */ 0x00000000,
};
}
\ No newline at end of file
diff --git a/firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp
index 22c42988b..9a734d278 100644
--- a/firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp
+++ b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp
@@ -1,5 +1,8 @@
-// RocketGod's Shopping Cart Lock app
-// https://betaskynet.com
+// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
+// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
+// If you can read this, you're a nerd. :P
+// Come join us at https://discord.gg/thepiratesreborn
+
#include "shoppingcart_lock.hpp"
using namespace portapack;
@@ -48,7 +51,6 @@ void ShoppingCartLock::stop() {
audio::output::stop();
log_event("... Resetting State Variables");
- transmitter_model.disable();
ready_signal = false;
thread_sync_complete = false;
looping = false;
@@ -110,18 +112,24 @@ std::string ShoppingCartLock::list_wav_files() {
}
void ShoppingCartLock::wait_for_thread() {
- uint32_t timeout = 100;
+ uint32_t timeout = 1000;
while (!ready_signal && timeout > 0) {
chThdYield();
timeout--;
}
+ if (!ready_signal) {
+ log_event("!!! Timeout waiting for ReplayThread");
+ }
}
void ShoppingCartLock::restart_playback() {
auto reader = std::make_unique();
std::string file_path = (wav_dir / current_file).string();
- if (!reader->open(file_path)) return;
+ if (!reader->open(file_path)) {
+ log_event("!!! Failed to reopen " + current_file + " for restart");
+ return;
+ }
replay_thread = std::make_unique(
std::move(reader),
@@ -133,9 +141,8 @@ void ShoppingCartLock::restart_playback() {
EventDispatcher::send_message(message);
});
- log_event(">> SENDING <<");
+ log_event(">> RESTARTING AUDIO <<");
audio::output::start();
- transmitter_model.enable();
}
void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
@@ -166,33 +173,24 @@ void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
wait_for_thread();
- log_event("... Configuring Baseband");
-
- const uint32_t bb_sample_rate = 1536000;
- const uint32_t decimation = bb_sample_rate / wav_sample_rate;
-
+ baseband::set_sample_rate(wav_sample_rate);
+ audio::set_rate(wav_sample_rate <= 12000 ? audio::Rate::Hz_12000 : wav_sample_rate <= 24000 ? audio::Rate::Hz_24000
+ : audio::Rate::Hz_48000);
baseband::set_audiotx_config(
- bb_sample_rate / decimation,
+ wav_sample_rate,
+ 0.0f,
0.0f,
- 5.0f,
wav_bits_per_sample,
wav_bits_per_sample,
0,
- true,
+ false,
false,
false,
false);
- baseband::set_sample_rate(wav_sample_rate);
-
- log_event("... Starting Audio Output");
audio::output::start();
- log_event("... Setting Max Volume");
- audio::headphone::set_volume(audio::headphone::volume_range().max);
-
- transmitter_model.enable();
-
- log_event(">>> Playback Started <<<");
+ volume_t max_volume = audio::headphone::volume_range().max;
+ audio::headphone::set_volume(max_volume);
}
ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
@@ -232,4 +230,4 @@ ShoppingCartLock::~ShoppingCartLock() {
baseband::shutdown();
}
-} // namespace ui::external_app::shoppingcart_lock
+} // namespace ui::external_app::shoppingcart_lock
\ No newline at end of file
diff --git a/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp
index abea3ca74..8bbb54e5e 100644
--- a/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp
+++ b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp
@@ -1,5 +1,8 @@
-// RocketGod's Shopping Cart Lock app
-// https://betaskynet.com
+// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
+// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
+// If you can read this, you're a nerd. :P
+// Come join us at https://discord.gg/thepiratesreborn
+
#pragma once
#include "ui_widget.hpp"
@@ -27,8 +30,8 @@ class ShoppingCartLock : public View {
void focus() override;
private:
- static constexpr size_t BUFFER_SIZE = 8192;
- static constexpr size_t NUM_BUFFERS = 8;
+ static constexpr size_t BUFFER_SIZE = 512;
+ static constexpr size_t NUM_BUFFERS = 2;
const std::string shoppingcart_lock_file{"shopping_cart_lock.wav"};
const std::string shoppingcart_unlock_file{"shopping_cart_unlock.wav"};
@@ -55,7 +58,7 @@ class ShoppingCartLock : public View {
void restart_playback();
MenuView menu_view{
- {0, 0, 240, 150},
+ {0, 0, screen_width, 150},
true};
Text text_empty{
diff --git a/firmware/application/external/snake/main.cpp b/firmware/application/external/snake/main.cpp
new file mode 100644
index 000000000..3c93935aa
--- /dev/null
+++ b/firmware/application/external/snake/main.cpp
@@ -0,0 +1,68 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui.hpp"
+#include "ui_snake.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::snake {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::snake
+
+extern "C" {
+
+__attribute__((section(".external_app.app_snake.application_information"), used)) application_information_t _application_information_snake = {
+ (uint8_t*)0x00000000,
+ ui::external_app::snake::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+ "Snake",
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x09,
+ 0x70,
+ 0xC7,
+ 0xFC,
+ 0xC9,
+ 0x06,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0xF0,
+ 0x01,
+ 0x00,
+ 0x3E,
+ 0x00,
+ 0x40,
+ 0xFC,
+ 0x40,
+ 0x02,
+ 0x3F,
+ 0x02,
+ 0x00,
+ 0x7C,
+ 0x80,
+ 0x80,
+ 0x7F,
+ },
+ ui::Color::green().v,
+ app_location_t::GAMES,
+ -1,
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+} // namespace ui::external_app::snake
\ No newline at end of file
diff --git a/firmware/application/external/snake/ui_snake.cpp b/firmware/application/external/snake/ui_snake.cpp
new file mode 100644
index 000000000..5e9f3ca4a
--- /dev/null
+++ b/firmware/application/external/snake/ui_snake.cpp
@@ -0,0 +1,267 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_snake.hpp"
+
+namespace ui::external_app::snake {
+
+void SnakeView::cls() {
+ painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
+}
+
+void SnakeView::background(int color) {
+ (void)color;
+}
+
+void SnakeView::fillrect(int x1, int y1, int x2, int y2, int color) {
+ painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void SnakeView::rect(int x1, int y1, int x2, int y2, int color) {
+ painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void SnakeView::check_game_timer() {
+ if (game_update_callback) {
+ if (++game_update_counter >= game_update_timeout) {
+ game_update_counter = 0;
+ game_timer_check();
+ }
+ }
+}
+
+void SnakeView::attach(double delay_sec) {
+ game_update_callback = true;
+ game_update_timeout = delay_sec * 60;
+}
+
+void SnakeView::detach() {
+ game_update_callback = false;
+}
+
+void SnakeView::game_timer_check() {
+ if (game_state == STATE_PLAYING) {
+ update_game();
+ }
+}
+
+void SnakeView::init_game() {
+ SCREEN_WIDTH = screen_width;
+ SCREEN_HEIGHT = screen_height;
+ GAME_AREA_HEIGHT = (SCREEN_HEIGHT - INFO_BAR_HEIGHT - 2);
+ GRID_WIDTH = ((SCREEN_WIDTH - 2) / SNAKE_SIZE);
+ GRID_HEIGHT = (GAME_AREA_HEIGHT / SNAKE_SIZE);
+ snake_x.resize(GRID_WIDTH * GRID_HEIGHT);
+ snake_y.resize(GRID_WIDTH * GRID_HEIGHT);
+ snake_x[0] = GRID_WIDTH / 2;
+ snake_y[0] = GRID_HEIGHT / 2;
+ snake_length = 1;
+ snake_dx = 1;
+ snake_dy = 0;
+ score = 0;
+ spawn_food();
+ if (game_state == STATE_MENU) {
+ show_menu();
+ } else if (game_state == STATE_PLAYING) {
+ draw_screen();
+ }
+}
+
+void SnakeView::spawn_food() {
+ bool valid;
+ do {
+ food_x = rand() % GRID_WIDTH;
+ food_y = rand() % GRID_HEIGHT;
+ valid = true;
+ for (int i = 0; i < snake_length; i++) {
+ if (snake_x[i] == food_x && snake_y[i] == food_y) {
+ valid = false;
+ break;
+ }
+ }
+ } while (!valid);
+}
+
+void SnakeView::update_game() {
+ int new_x = snake_x[0] + snake_dx;
+ int new_y = snake_y[0] + snake_dy;
+ bool ate_food = (new_x == food_x && new_y == food_y);
+
+ int tail_x = snake_x[snake_length - 1];
+ int tail_y = snake_y[snake_length - 1];
+
+ for (int i = snake_length - 1; i > 0; i--) {
+ snake_x[i] = snake_x[i - 1];
+ snake_y[i] = snake_y[i - 1];
+ }
+
+ snake_x[0] = new_x;
+ snake_y[0] = new_y;
+
+ if (ate_food) {
+ snake_x[snake_length] = tail_x;
+ snake_y[snake_length] = tail_y;
+ snake_length++;
+ score += 10;
+ spawn_food();
+ draw_food();
+ } else {
+ erase_tail(tail_x, tail_y);
+ }
+
+ draw_snake();
+ draw_score();
+
+ if (check_collision()) {
+ draw_borders();
+ game_state = STATE_GAME_OVER;
+ show_game_over();
+ return;
+ }
+}
+
+bool SnakeView::check_collision() {
+ if (snake_x[0] < 0 || snake_x[0] >= GRID_WIDTH || snake_y[0] < 0 || snake_y[0] >= GRID_HEIGHT) {
+ return true;
+ }
+ for (int i = 1; i < snake_length; i++) {
+ if (snake_x[0] == snake_x[i] && snake_y[0] == snake_y[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SnakeView::draw_screen() {
+ cls();
+ background(COLOR_BACKGROUND);
+ draw_borders();
+ draw_full_snake();
+ draw_food();
+ draw_score();
+}
+
+void SnakeView::draw_snake() {
+ fillrect(1 + snake_x[0] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE,
+ 1 + snake_x[0] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
+}
+
+void SnakeView::draw_full_snake() {
+ for (int i = 0; i < snake_length; i++) {
+ fillrect(1 + snake_x[i] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE,
+ 1 + snake_x[i] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
+ }
+}
+
+void SnakeView::erase_tail(int x, int y) {
+ fillrect(1 + x * SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE,
+ 1 + x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
+}
+
+void SnakeView::draw_food() {
+ fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
+ 1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_FOOD);
+}
+
+void SnakeView::erase_food() {
+ fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
+ 1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
+}
+
+void SnakeView::draw_score() {
+ auto style = *ui::Theme::getInstance()->fg_blue;
+ painter.draw_string({5, 5}, style, "Score: " + std::to_string(score));
+}
+
+void SnakeView::draw_borders() {
+ rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
+ rect(0, GAME_AREA_TOP, SCREEN_WIDTH, SCREEN_HEIGHT, COLOR_BORDER);
+}
+
+void SnakeView::show_menu() {
+ cls();
+ background(COLOR_BACKGROUND);
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_green = *ui::Theme::getInstance()->fg_green;
+ auto style_blue = *ui::Theme::getInstance()->fg_blue;
+ painter.draw_string({50, 40}, style_yellow, "* * * SNAKE * * *");
+ painter.draw_string({0, 120}, style_blue, "USE THE D-PAD TO MOVE");
+ painter.draw_string({0, 150}, style_blue, "EAT THE RED SQUARES TO GROW");
+ painter.draw_string({0, 180}, style_blue, "DON'T HIT THE WALLS OR SELF");
+ painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **");
+}
+
+void SnakeView::show_game_over() {
+ cls();
+ background(COLOR_BACKGROUND);
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_green = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({75, 90}, style_red, "GAME OVER");
+ painter.draw_string({74, 150}, style_yellow, "SCORE: " + std::to_string(score));
+ painter.draw_string({20, 220}, style_green, "PRESS SELECT TO RESTART");
+ wait(1);
+}
+
+SnakeView::SnakeView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&dummy});
+ attach(1.0 / 5.0);
+}
+
+void SnakeView::on_show() {
+}
+
+void SnakeView::paint(Painter& painter) {
+ (void)painter;
+ if (!initialized) {
+ initialized = true;
+ std::srand(LPC_RTC->CTIME0);
+ init_game();
+ }
+}
+
+void SnakeView::frame_sync() {
+ check_game_timer();
+ set_dirty();
+}
+
+bool SnakeView::on_key(const KeyEvent key) {
+ if (key == KeyEvent::Select) {
+ if (game_state == STATE_MENU || game_state == STATE_GAME_OVER) {
+ game_state = STATE_PLAYING;
+ init_game();
+ }
+ } else if (game_state == STATE_PLAYING) {
+ if (key == KeyEvent::Left) {
+ if (snake_dx == 0) {
+ snake_dx = -1;
+ snake_dy = 0;
+ }
+ } else if (key == KeyEvent::Right) {
+ if (snake_dx == 0) {
+ snake_dx = 1;
+ snake_dy = 0;
+ }
+ } else if (key == KeyEvent::Up) {
+ if (snake_dy == 0) {
+ snake_dx = 0;
+ snake_dy = -1;
+ }
+ } else if (key == KeyEvent::Down) {
+ if (snake_dy == 0) {
+ snake_dx = 0;
+ snake_dy = 1;
+ }
+ }
+ }
+ set_dirty();
+ return true;
+}
+
+} // namespace ui::external_app::snake
\ No newline at end of file
diff --git a/firmware/application/external/snake/ui_snake.hpp b/firmware/application/external/snake/ui_snake.hpp
new file mode 100644
index 000000000..ad2f1cc23
--- /dev/null
+++ b/firmware/application/external/snake/ui_snake.hpp
@@ -0,0 +1,133 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_SNAKE_H__
+#define __UI_SNAKE_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "event_m0.hpp"
+#include "message.hpp"
+#include "irq_controls.hpp"
+#include "random.hpp"
+#include "lpc43xx_cpp.hpp"
+#include "ui_widget.hpp"
+
+namespace ui::external_app::snake {
+
+enum {
+ White,
+ Blue,
+ Yellow,
+ Purple,
+ Green,
+ Red,
+ Maroon,
+ Orange,
+ Black,
+};
+
+#define wait(x) chThdSleepMilliseconds(x * 1000)
+
+#define SNAKE_SIZE 10
+#define INFO_BAR_HEIGHT 25
+#define GAME_AREA_TOP (INFO_BAR_HEIGHT + 1)
+#define STATE_MENU 0
+#define STATE_PLAYING 1
+#define STATE_GAME_OVER 2
+
+#define COLOR_BACKGROUND Black
+#define COLOR_SNAKE Green
+#define COLOR_FOOD Red
+#define COLOR_BORDER White
+
+class SnakeView : public View {
+ public:
+ SnakeView(NavigationView& nav);
+ void on_show() override;
+
+ std::string title() const override { return "Snake"; }
+
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_key(KeyEvent key) override;
+
+ void cls();
+ void background(int color);
+ void fillrect(int x1, int y1, int x2, int y2, int color);
+ void rect(int x1, int y1, int x2, int y2, int color);
+ void game_timer_check();
+ void init_game();
+ void update_game();
+ void draw_screen();
+ void draw_snake();
+ void draw_full_snake();
+ void erase_tail(int x, int y);
+ void draw_food();
+ void erase_food();
+ void draw_score();
+ void draw_borders();
+ void spawn_food();
+ bool check_collision();
+ void show_menu();
+ void show_game_over();
+ void check_game_timer();
+
+ void attach(double delay_sec);
+ void detach();
+
+ private:
+ const Color pp_colors[9] = {
+ Color::white(),
+ Color::blue(),
+ Color::yellow(),
+ Color::purple(),
+ Color::green(),
+ Color::red(),
+ Color::magenta(),
+ Color::orange(),
+ Color::black(),
+ };
+ NavigationView& nav_;
+ Painter painter{};
+
+ std::vector snake_x{}; //[GRID_WIDTH * GRID_HEIGHT];
+ std::vector snake_y{}; //[GRID_WIDTH * GRID_HEIGHT];
+ int snake_length = 1;
+ int snake_dx = 1;
+ int snake_dy = 0;
+ int food_x = 0, food_y = 0;
+ int score = 0;
+ int game_state = STATE_MENU;
+ bool initialized = false;
+
+ int SCREEN_WIDTH = 0;
+ int SCREEN_HEIGHT = 0;
+ int GAME_AREA_HEIGHT = 0; //(SCREEN_HEIGHT - INFO_BAR_HEIGHT - 2);
+ int GRID_WIDTH = 0; // ((SCREEN_WIDTH - 2) / SNAKE_SIZE);
+ int GRID_HEIGHT = 0; //(GAME_AREA_HEIGHT / SNAKE_SIZE);
+
+ bool game_update_callback = false;
+ double game_update_timeout = 0;
+ uint32_t game_update_counter = 0;
+
+ Button dummy{
+ {screen_width, 0, 0, 0},
+ ""};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+
+} // namespace ui::external_app::snake
+
+#endif /* __UI_SNAKE_H__ */
\ No newline at end of file
diff --git a/firmware/application/external/spaceinv/main.cpp b/firmware/application/external/spaceinv/main.cpp
new file mode 100644
index 000000000..961e9268d
--- /dev/null
+++ b/firmware/application/external/spaceinv/main.cpp
@@ -0,0 +1,55 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui.hpp"
+#include "ui_spaceinv.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::spaceinv {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::spaceinv
+
+extern "C" {
+
+__attribute__((section(".external_app.app_spaceinv.application_information"), used)) application_information_t _application_information_spaceinv = {
+ (uint8_t*)0x00000000,
+ ui::external_app::spaceinv::initialize_app,
+ CURRENT_HEADER_VERSION,
+ VERSION_MD5,
+
+ "Space Invaders",
+ {
+ // Space Invader alien icon 16x16
+ 0x00, 0x18, // .......##.......
+ 0x00, 0x3C, // ......####......
+ 0x00, 0x7E, // .....######.....
+ 0x00, 0xDB, // ....##.##.##....
+ 0x00, 0xFF, // ....########....
+ 0x00, 0xFF, // ....########....
+ 0x00, 0x24, // ......#..#......
+ 0x00, 0x66, // .....##..##.....
+ 0x00, 0x42, // .....#....#.....
+ 0x00, 0x00, // ................
+ 0x00, 0x24, // ......#..#......
+ 0x00, 0x18, // .......##.......
+ 0x00, 0x24, // ......#..#......
+ 0x00, 0x42, // .....#....#.....
+ 0x00, 0x81, // ....#......#....
+ 0x00, 0x00, // ................
+ },
+ ui::Color::magenta().v,
+ app_location_t::GAMES,
+ -1,
+
+ {0, 0, 0, 0},
+ 0x00000000,
+};
+} // namespace ui::external_app::spaceinv
\ No newline at end of file
diff --git a/firmware/application/external/spaceinv/ui_spaceinv.cpp b/firmware/application/external/spaceinv/ui_spaceinv.cpp
new file mode 100644
index 000000000..9c02d9dea
--- /dev/null
+++ b/firmware/application/external/spaceinv/ui_spaceinv.cpp
@@ -0,0 +1,879 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#include "ui_spaceinv.hpp"
+
+namespace ui::external_app::spaceinv {
+
+// Game constants
+#define PLAYER_WIDTH 26
+#define PLAYER_HEIGHT 16
+#define PLAYER_Y 280
+#define BULLET_WIDTH 3
+#define BULLET_HEIGHT 10
+#define BULLET_SPEED 8
+#define MAX_BULLETS 3
+#define INVADER_WIDTH 20
+#define INVADER_HEIGHT 16
+#define INVADER_ROWS 5 // Classic 5 rows
+#define INVADER_COLS 6 // Reduced for more movement space on narrow PP screen
+#define INVADER_GAP_X 10
+#define INVADER_GAP_Y 8 // Gap between invaders
+#define MAX_ENEMY_BULLETS 3
+#define ENEMY_BULLET_SPEED 3
+
+// Game state
+static int game_state = 0; // 0=menu, 1=playing, 2=game over, 3=wave_complete
+static bool initialized = false;
+static int player_x = 120;
+static uint32_t score = 0;
+static int lives = 3;
+static uint32_t wave = 1;
+static uint32_t speed_bonus = 0;
+static uint32_t wave_complete_timer = 0;
+
+// Cannon Bullets
+static int bullet_x[MAX_BULLETS] = {0, 0, 0};
+static int bullet_y[MAX_BULLETS] = {0, 0, 0};
+static bool bullet_active[MAX_BULLETS] = {false, false, false};
+
+// Enemy bullets
+static int enemy_bullet_x[MAX_ENEMY_BULLETS] = {0, 0, 0};
+static int enemy_bullet_y[MAX_ENEMY_BULLETS] = {0, 0, 0};
+static bool enemy_bullet_active[MAX_ENEMY_BULLETS] = {false, false, false};
+static uint32_t enemy_fire_counter = 0;
+
+// Invaders
+static bool invaders[INVADER_ROWS][INVADER_COLS];
+static int invaders_x = 40;
+static int invaders_y = 60;
+static int invader_direction = 1;
+static uint32_t invader_move_counter = 0;
+static uint32_t invader_move_delay = 30;
+static bool invader_animation_frame = false;
+
+// Menu state
+static bool menu_initialized = false;
+static bool game_over_initialized = false;
+static bool blink_state = true;
+static uint32_t blink_counter = 0;
+static int16_t prompt_x = 0;
+
+// Timer
+static Ticker game_timer;
+static Callback game_update_callback = nullptr;
+static uint32_t game_update_timeout = 0;
+static uint32_t game_update_counter = 0;
+
+// Painter
+static Painter painter;
+
+// For high score access
+static SpaceInvadersView* current_instance = nullptr;
+
+void check_game_timer() {
+ if (game_update_callback) {
+ if (++game_update_counter >= game_update_timeout) {
+ game_update_counter = 0;
+ game_update_callback();
+ }
+ }
+}
+
+void Ticker::attach(Callback func, double delay_sec) {
+ game_update_callback = func;
+ game_update_timeout = delay_sec * 60;
+}
+
+void Ticker::detach() {
+ game_update_callback = nullptr;
+}
+
+static void init_invaders() {
+ // Initialize invader array
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ invaders[row][col] = true;
+ }
+ }
+ invaders_x = 40;
+ invaders_y = 60;
+ invader_direction = 1;
+ invader_move_counter = 0;
+
+ // Clear any active enemy bullets
+ for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
+ enemy_bullet_active[i] = false;
+ }
+
+ // Clear any active player bullets
+ for (int i = 0; i < MAX_BULLETS; i++) {
+ bullet_active[i] = false;
+ }
+}
+
+static void save_high_score() {
+ if (current_instance && score > current_instance->highScore) {
+ current_instance->highScore = score;
+ // Settings are automatically saved when the view is destroyed
+ }
+}
+
+static void init_menu() {
+ painter.fill_rectangle({0, 0, 240, 320}, Color::black());
+
+ auto style_green = *ui::Theme::getInstance()->fg_green;
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
+
+ int16_t screen_width = 240;
+ int16_t title_x = (screen_width - 15 * 8) / 2;
+ int16_t divider_width = 24 * 8;
+ int16_t divider_x = (screen_width - divider_width) / 2;
+ int16_t instruction_width = 22 * 8;
+ int16_t instruction_x = (screen_width - instruction_width) / 2;
+ int16_t prompt_width = 12 * 8;
+ prompt_x = (screen_width - prompt_width) / 2;
+
+ painter.fill_rectangle({0, 30, screen_width, 30}, Color::black());
+ painter.draw_string({title_x, 42}, style_green, "SPACE INVADERS");
+
+ painter.draw_string({divider_x, 70}, style_yellow, "========================");
+
+ painter.fill_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::black());
+ painter.draw_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::white());
+
+ painter.draw_string({instruction_x, 110}, style_cyan, " ROTARY: MOVE SHIP");
+ painter.draw_string({instruction_x, 130}, style_cyan, " SELECT: FIRE");
+ painter.draw_string({instruction_x, 150}, style_cyan, " DEFEND EARTH!");
+
+ // Draw point values
+ auto style_purple = *ui::Theme::getInstance()->fg_magenta;
+ painter.draw_string({50, 190}, style_purple, "TOP ROW = 30 PTS");
+ painter.draw_string({50, 205}, style_yellow, "MID ROW = 20 PTS");
+ painter.draw_string({50, 220}, style_green, "BOT ROW = 10 PTS");
+
+ // Draw high score
+ if (current_instance) {
+ auto style_white = *ui::Theme::getInstance()->fg_light;
+ std::string high_score_text = "HIGH SCORE: " + std::to_string(current_instance->highScore);
+ int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
+ painter.draw_string({high_score_x, 240}, style_white, high_score_text);
+ }
+
+ menu_initialized = true;
+}
+
+static void init_game_over() {
+ painter.fill_rectangle({0, 0, 240, 320}, Color::black());
+
+ auto style_red = *ui::Theme::getInstance()->fg_red;
+ auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
+ auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
+ auto style_white = *ui::Theme::getInstance()->fg_light;
+
+ int16_t screen_width = 240;
+
+ // Game Over title
+ int16_t title_x = (screen_width - 9 * 8) / 2;
+ painter.draw_string({title_x, 42}, style_red, "GAME OVER");
+
+ // Divider
+ int16_t divider_width = 24 * 8;
+ int16_t divider_x = (screen_width - divider_width) / 2;
+ painter.draw_string({divider_x, 70}, style_yellow, "========================");
+
+ // Score box
+ int16_t box_width = 22 * 8;
+ int16_t box_x = (screen_width - box_width) / 2;
+ painter.fill_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::black());
+ painter.draw_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::white());
+
+ // Display scores
+ std::string score_text = "SCORE: " + std::to_string(score);
+ int16_t score_x = (screen_width - score_text.length() * 8) / 2;
+ painter.draw_string({score_x, 120}, style_cyan, score_text);
+
+ std::string wave_text = "WAVE: " + std::to_string(wave);
+ int16_t wave_x = (screen_width - wave_text.length() * 8) / 2;
+ painter.draw_string({wave_x, 140}, style_cyan, wave_text);
+
+ // High score section
+ if (current_instance) {
+ if (score > current_instance->highScore) {
+ // New high score!
+ std::string new_high = "NEW HIGH SCORE!";
+ int16_t new_high_x = (screen_width - new_high.length() * 8) / 2;
+ painter.draw_string({new_high_x, 200}, style_yellow, new_high);
+
+ std::string high_score_text = std::to_string(score);
+ int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
+ painter.draw_string({high_score_x, 220}, style_yellow, high_score_text);
+ } else {
+ // Show existing high score
+ std::string high_label = "HIGH SCORE:";
+ int16_t label_x = (screen_width - high_label.length() * 8) / 2;
+ painter.draw_string({label_x, 200}, style_white, high_label);
+
+ std::string high_score_text = std::to_string(current_instance->highScore);
+ int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
+ painter.draw_string({high_score_x, 220}, style_white, high_score_text);
+ }
+ }
+
+ game_over_initialized = true;
+}
+
+static void draw_player() {
+ // Clear the entire player area
+ painter.fill_rectangle({0, PLAYER_Y - 4, 240, PLAYER_HEIGHT + 8}, Color::black());
+
+ // Draw the classic Space Invaders cannon
+ Color green = Color::green();
+
+ // Top part - the cannon barrel
+ painter.draw_hline({player_x + 12, PLAYER_Y}, 2, green);
+ painter.draw_hline({player_x + 12, PLAYER_Y + 1}, 2, green);
+
+ // Upper body
+ painter.draw_hline({player_x + 11, PLAYER_Y + 2}, 4, green);
+ painter.draw_hline({player_x + 11, PLAYER_Y + 3}, 4, green);
+ painter.draw_hline({player_x + 10, PLAYER_Y + 4}, 6, green);
+ painter.draw_hline({player_x + 10, PLAYER_Y + 5}, 6, green);
+
+ // Main body
+ painter.draw_hline({player_x + 2, PLAYER_Y + 6}, 22, green);
+ painter.draw_hline({player_x + 2, PLAYER_Y + 7}, 22, green);
+ painter.draw_hline({player_x + 1, PLAYER_Y + 8}, 24, green);
+ painter.draw_hline({player_x + 1, PLAYER_Y + 9}, 24, green);
+ painter.draw_hline({player_x, PLAYER_Y + 10}, 26, green);
+ painter.draw_hline({player_x, PLAYER_Y + 11}, 26, green);
+ painter.draw_hline({player_x, PLAYER_Y + 12}, 26, green);
+ painter.draw_hline({player_x, PLAYER_Y + 13}, 26, green);
+ painter.draw_hline({player_x, PLAYER_Y + 14}, 26, green);
+ painter.draw_hline({player_x, PLAYER_Y + 15}, 26, green);
+}
+
+static void draw_invader(int row, int col) {
+ int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
+ int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
+
+ // Clear the area first
+ painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
+
+ // Different colors and shapes for different rows
+ Color color;
+ if (row == 0) {
+ color = Color::red(); // Top row - 30 points
+ } else if (row == 1 || row == 2) {
+ color = Color::yellow(); // Middle rows - 20 points
+ } else {
+ color = Color::green(); // Bottom rows - 10 points
+ }
+
+ // Draw different invader types based on row
+ if (row == 0) {
+ // Top row - squid-like invader (30 points)
+ if (invader_animation_frame) {
+ // Frame 1 - arms down
+ painter.draw_hline({x + 8, y + 2}, 4, color);
+ painter.draw_hline({x + 6, y + 3}, 8, color);
+ painter.draw_hline({x + 4, y + 4}, 12, color);
+ painter.draw_hline({x + 2, y + 5}, 16, color);
+ painter.draw_hline({x + 2, y + 6}, 16, color);
+ painter.draw_hline({x, y + 7}, 20, color);
+ painter.draw_hline({x, y + 8}, 20, color);
+ painter.draw_hline({x + 2, y + 9}, 2, color);
+ painter.draw_hline({x + 6, y + 9}, 8, color);
+ painter.draw_hline({x + 16, y + 9}, 2, color);
+ painter.draw_hline({x + 4, y + 10}, 2, color);
+ painter.draw_hline({x + 14, y + 10}, 2, color);
+ } else {
+ // Frame 2 - arms up
+ painter.draw_hline({x + 8, y + 2}, 4, color);
+ painter.draw_hline({x + 6, y + 3}, 8, color);
+ painter.draw_hline({x + 4, y + 4}, 12, color);
+ painter.draw_hline({x + 2, y + 5}, 16, color);
+ painter.draw_hline({x + 2, y + 6}, 16, color);
+ painter.draw_hline({x, y + 7}, 20, color);
+ painter.draw_hline({x, y + 8}, 20, color);
+ painter.draw_hline({x + 4, y + 9}, 2, color);
+ painter.draw_hline({x + 8, y + 9}, 4, color);
+ painter.draw_hline({x + 14, y + 9}, 2, color);
+ painter.draw_hline({x + 2, y + 10}, 2, color);
+ painter.draw_hline({x + 16, y + 10}, 2, color);
+ }
+ } else if (row == 1 || row == 2) {
+ // Middle rows - crab-like invader (20 points)
+ if (invader_animation_frame) {
+ // Frame 1 - arms in
+ painter.draw_hline({x + 4, y + 2}, 2, color);
+ painter.draw_hline({x + 14, y + 2}, 2, color);
+ painter.draw_hline({x + 2, y + 3}, 2, color);
+ painter.draw_hline({x + 6, y + 3}, 8, color);
+ painter.draw_hline({x + 16, y + 3}, 2, color);
+ painter.draw_hline({x + 2, y + 4}, 16, color);
+ painter.draw_hline({x, y + 5}, 6, color);
+ painter.draw_hline({x + 8, y + 5}, 4, color);
+ painter.draw_hline({x + 14, y + 5}, 6, color);
+ painter.draw_hline({x, y + 6}, 20, color);
+ painter.draw_hline({x + 2, y + 7}, 16, color);
+ painter.draw_hline({x + 4, y + 8}, 2, color);
+ painter.draw_hline({x + 14, y + 8}, 2, color);
+ painter.draw_hline({x + 2, y + 9}, 4, color);
+ painter.draw_hline({x + 14, y + 9}, 4, color);
+ } else {
+ // Frame 2 - arms out
+ painter.draw_hline({x + 4, y + 2}, 2, color);
+ painter.draw_hline({x + 14, y + 2}, 2, color);
+ painter.draw_hline({x + 2, y + 3}, 2, color);
+ painter.draw_hline({x + 6, y + 3}, 8, color);
+ painter.draw_hline({x + 16, y + 3}, 2, color);
+ painter.draw_hline({x + 2, y + 4}, 16, color);
+ painter.draw_hline({x, y + 5}, 6, color);
+ painter.draw_hline({x + 8, y + 5}, 4, color);
+ painter.draw_hline({x + 14, y + 5}, 6, color);
+ painter.draw_hline({x, y + 6}, 20, color);
+ painter.draw_hline({x + 2, y + 7}, 16, color);
+ painter.draw_hline({x + 4, y + 8}, 2, color);
+ painter.draw_hline({x + 14, y + 8}, 2, color);
+ painter.draw_hline({x, y + 9}, 2, color);
+ painter.draw_hline({x + 6, y + 9}, 2, color);
+ painter.draw_hline({x + 12, y + 9}, 2, color);
+ painter.draw_hline({x + 18, y + 9}, 2, color);
+ }
+ } else {
+ // Bottom rows - octopus-like invader (10 points)
+ if (invader_animation_frame) {
+ // Frame 1
+ painter.draw_hline({x + 6, y + 2}, 8, color);
+ painter.draw_hline({x + 4, y + 3}, 12, color);
+ painter.draw_hline({x + 2, y + 4}, 16, color);
+ painter.draw_hline({x, y + 5}, 20, color);
+ painter.draw_hline({x, y + 6}, 20, color);
+ painter.draw_hline({x + 4, y + 7}, 4, color);
+ painter.draw_hline({x + 12, y + 7}, 4, color);
+ painter.draw_hline({x + 2, y + 8}, 4, color);
+ painter.draw_hline({x + 8, y + 8}, 4, color);
+ painter.draw_hline({x + 14, y + 8}, 4, color);
+ painter.draw_hline({x + 4, y + 9}, 2, color);
+ painter.draw_hline({x + 14, y + 9}, 2, color);
+ } else {
+ // Frame 2
+ painter.draw_hline({x + 6, y + 2}, 8, color);
+ painter.draw_hline({x + 4, y + 3}, 12, color);
+ painter.draw_hline({x + 2, y + 4}, 16, color);
+ painter.draw_hline({x, y + 5}, 20, color);
+ painter.draw_hline({x, y + 6}, 20, color);
+ painter.draw_hline({x + 4, y + 7}, 4, color);
+ painter.draw_hline({x + 12, y + 7}, 4, color);
+ painter.draw_hline({x + 2, y + 8}, 4, color);
+ painter.draw_hline({x + 8, y + 8}, 4, color);
+ painter.draw_hline({x + 14, y + 8}, 4, color);
+ painter.draw_hline({x + 2, y + 9}, 2, color);
+ painter.draw_hline({x + 16, y + 9}, 2, color);
+ }
+ }
+}
+
+static void draw_all_invaders() {
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) {
+ draw_invader(row, col);
+ }
+ }
+ }
+}
+
+static void clear_all_invaders() {
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) {
+ int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
+ int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
+ painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
+ }
+ }
+ }
+}
+
+static void clear_all_enemy_bullets() {
+ // Clear any visible enemy bullets
+ for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
+ if (enemy_bullet_active[i]) {
+ painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
+ enemy_bullet_active[i] = false;
+ }
+ }
+}
+
+static void fire_enemy_bullet() {
+ // Find a random invader to fire from
+ int active_count = 0;
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) active_count++;
+ }
+ }
+
+ if (active_count == 0) return;
+
+ int shooter = rand() % active_count;
+ int count = 0;
+
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) {
+ if (count == shooter) {
+ // Fire from this invader
+ for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
+ if (!enemy_bullet_active[i]) {
+ int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
+ int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
+ enemy_bullet_x[i] = x + INVADER_WIDTH / 2;
+ enemy_bullet_y[i] = y + INVADER_HEIGHT;
+ enemy_bullet_active[i] = true;
+ return;
+ }
+ }
+ }
+ count++;
+ }
+ }
+ }
+}
+
+static void update_enemy_bullets() {
+ for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
+ if (enemy_bullet_active[i]) {
+ // Clear old position - but protect the border line
+ if (enemy_bullet_y[i] != 49) { // Don't clear if we're exactly on the border line
+ painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
+ }
+
+ enemy_bullet_y[i] += ENEMY_BULLET_SPEED;
+
+ if (enemy_bullet_y[i] > 320) {
+ enemy_bullet_active[i] = false;
+ } else {
+ // Draw at new position
+ painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::red());
+
+ // Check collision with player
+ if (enemy_bullet_x[i] >= player_x &&
+ enemy_bullet_x[i] <= player_x + PLAYER_WIDTH &&
+ enemy_bullet_y[i] >= PLAYER_Y &&
+ enemy_bullet_y[i] <= PLAYER_Y + PLAYER_HEIGHT) {
+ // Player hit!
+ enemy_bullet_active[i] = false;
+ lives--;
+
+ // Update lives display
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.fill_rectangle({5, 30, 100, 20}, Color::black());
+ painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
+
+ if (lives <= 0) {
+ game_state = 2; // Game over
+ save_high_score();
+ }
+ }
+ }
+ }
+ }
+}
+
+static void update_invaders() {
+ uint32_t adjusted_delay = (speed_bonus >= invader_move_delay) ? 1 : invader_move_delay - speed_bonus;
+
+ if (++invader_move_counter >= adjusted_delay) {
+ invader_move_counter = 0;
+
+ // Clear old positions
+ clear_all_invaders();
+
+ // Check bounds - find the actual leftmost and rightmost invaders
+ int leftmost = INVADER_COLS;
+ int rightmost = -1;
+
+ for (int col = 0; col < INVADER_COLS; col++) {
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ if (invaders[row][col]) {
+ if (col < leftmost) leftmost = col;
+ if (col > rightmost) rightmost = col;
+ }
+ }
+ }
+
+ bool hit_edge = false;
+ if (invader_direction > 0) {
+ // Moving right - check rightmost invader
+ int right_edge = invaders_x + rightmost * (INVADER_WIDTH + INVADER_GAP_X) + INVADER_WIDTH;
+ if (right_edge >= 240) {
+ hit_edge = true;
+ }
+ } else {
+ // Moving left - check leftmost invader
+ int left_edge = invaders_x + leftmost * (INVADER_WIDTH + INVADER_GAP_X);
+ if (left_edge <= 0) {
+ hit_edge = true;
+ }
+ }
+
+ // Move invaders
+ if (hit_edge) {
+ invaders_y += 15; // Move down
+ invader_direction = -invader_direction;
+ } else {
+ invaders_x += invader_direction * 8; // Horizontal movement
+ }
+
+ // Toggle animation frame
+ invader_animation_frame = !invader_animation_frame;
+
+ // Draw new positions
+ draw_all_invaders();
+ }
+
+ // Enemy firing logic - only in hard mode or always with lower frequency
+ if (!current_instance || !current_instance->easy_mode) {
+ if (++enemy_fire_counter >= 120) { // Fire every 2 seconds at 60fps
+ enemy_fire_counter = 0;
+ fire_enemy_bullet();
+ }
+ }
+}
+
+static void fire_bullet() {
+ for (int i = 0; i < MAX_BULLETS; i++) {
+ if (!bullet_active[i]) {
+ bullet_active[i] = true;
+ bullet_x[i] = player_x + PLAYER_WIDTH / 2 - BULLET_WIDTH / 2;
+ bullet_y[i] = PLAYER_Y - BULLET_HEIGHT;
+ break;
+ }
+ }
+}
+
+static void update_bullets() {
+ for (int i = 0; i < MAX_BULLETS; i++) {
+ if (bullet_active[i]) {
+ painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
+
+ bullet_y[i] -= BULLET_SPEED;
+
+ if (bullet_y[i] < 50) {
+ bullet_active[i] = false;
+ } else {
+ painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::white());
+ }
+ }
+ }
+}
+
+static void check_collisions() {
+ for (int i = 0; i < MAX_BULLETS; i++) {
+ if (!bullet_active[i]) continue;
+
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (!invaders[row][col]) continue;
+
+ int inv_x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
+ int inv_y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
+
+ // Simple collision check
+ if (bullet_x[i] < inv_x + INVADER_WIDTH &&
+ bullet_x[i] + BULLET_WIDTH > inv_x &&
+ bullet_y[i] < inv_y + INVADER_HEIGHT &&
+ bullet_y[i] + BULLET_HEIGHT > inv_y) {
+ // Hit!
+ bullet_active[i] = false;
+ painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
+
+ invaders[row][col] = false;
+ painter.fill_rectangle({inv_x, inv_y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
+
+ // Score based on row: top=30, middle=20, bottom=10
+ if (row == 0) {
+ score += 30;
+ } else if (row == 1 || row == 2) {
+ score += 20;
+ } else {
+ score += 10;
+ }
+
+ return;
+ }
+ }
+ }
+ }
+}
+
+static void check_wave_complete() {
+ // Check if all invaders are destroyed
+ bool all_destroyed = true;
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) {
+ all_destroyed = false;
+ break;
+ }
+ }
+ if (!all_destroyed) break;
+ }
+
+ if (all_destroyed) {
+ // Next wave!
+ wave++;
+ speed_bonus = (wave - 1) * 3; // Each wave is slightly faster
+ if (speed_bonus > 15) speed_bonus = 15; // Cap the speed increase
+
+ // Clear any enemy bullets before transitioning
+ clear_all_enemy_bullets();
+
+ // Set state to wave complete and start timer
+ game_state = 3;
+ wave_complete_timer = 60; // 1 second at 60fps
+
+ // Clear screen and show wave message - but preserve the border line
+ painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line at 49
+
+ // Redraw the border line to ensure it's intact - stupid bullet clearing wants to damage it
+ painter.draw_hline({0, 49}, 240, Color::white());
+
+ auto style = *ui::Theme::getInstance()->fg_green;
+ std::string wave_text = "WAVE " + std::to_string(wave);
+ int wave_x = (240 - wave_text.length() * 8) / 2;
+ painter.draw_string({wave_x, 150}, style, wave_text);
+
+ return;
+ }
+
+ // Check if invaders reached player
+ for (int row = 0; row < INVADER_ROWS; row++) {
+ for (int col = 0; col < INVADER_COLS; col++) {
+ if (invaders[row][col]) {
+ int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
+ if (y + INVADER_HEIGHT >= PLAYER_Y) { // Actual collision with player
+ game_state = 2; // Game over
+ save_high_score();
+ return;
+ }
+ }
+ }
+ }
+}
+
+void game_timer_check() {
+ if (game_state == 0) {
+ // Menu state
+ if (!menu_initialized) {
+ init_menu();
+ }
+
+ if (++blink_counter >= 30) {
+ blink_counter = 0;
+ blink_state = !blink_state;
+
+ auto style = *ui::Theme::getInstance()->fg_red;
+ if (blink_state) {
+ painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
+ } else {
+ painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
+ }
+ }
+ } else if (game_state == 1) {
+ // Playing state
+ update_bullets();
+ update_enemy_bullets();
+ update_invaders();
+ check_collisions();
+ check_wave_complete();
+
+ // Update score display
+ static uint32_t last_score = 999999;
+ if (score != last_score) {
+ last_score = score;
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.fill_rectangle({5, 10, 100, 20}, Color::black());
+ painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
+
+ // Redraw border line after score update (in case it was damaged) - stupid bullet clearing wants to damage it
+ painter.draw_hline({0, 49}, 240, Color::white());
+ }
+
+ // Periodically redraw the border line to fix any damage because it's a pain in the ass
+ static uint32_t border_redraw_counter = 0;
+ if (++border_redraw_counter >= 60) { // Once per second - might make less if causing flickering
+ border_redraw_counter = 0;
+ painter.draw_hline({0, 49}, 240, Color::white());
+ }
+ } else if (game_state == 2) {
+ // Game over state
+ if (!game_over_initialized) {
+ init_game_over();
+ }
+
+ if (++blink_counter >= 30) {
+ blink_counter = 0;
+ blink_state = !blink_state;
+
+ auto style = *ui::Theme::getInstance()->fg_red;
+ if (blink_state) {
+ painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
+ } else {
+ painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
+ }
+ }
+ } else if (game_state == 3) {
+ // Wave complete state - wait for timer
+ if (--wave_complete_timer <= 0) {
+ // Timer expired, start next wave
+ game_state = 1;
+
+ // Clear and redraw game area - preserve border line
+ painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line
+
+ // Restore UI
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
+ painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
+
+ // Redraw the border line in case it was damaged again
+ painter.draw_hline({0, 49}, 240, Color::white());
+
+ // Initialize new wave of invaders
+ init_invaders();
+ draw_all_invaders();
+ draw_player();
+ }
+ }
+}
+
+SpaceInvadersView::SpaceInvadersView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&dummy, &button_difficulty});
+ current_instance = this;
+ game_timer.attach(&game_timer_check, 1.0 / 60.0);
+
+ // Update button text based on loaded setting
+ button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
+
+ button_difficulty.on_select = [this](Button&) {
+ easy_mode = !easy_mode;
+ button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
+ // Settings will be saved when the view is destroyed
+ };
+}
+
+SpaceInvadersView::~SpaceInvadersView() {
+ // Settings are automatically saved when destroyed
+ current_instance = nullptr;
+}
+
+void SpaceInvadersView::on_show() {
+}
+
+void SpaceInvadersView::paint(Painter& painter) {
+ (void)painter;
+
+ if (!initialized) {
+ initialized = true;
+ game_state = 0;
+ menu_initialized = false;
+ game_over_initialized = false;
+ blink_state = true;
+ blink_counter = 0;
+ player_x = 107;
+ score = 0;
+ lives = 3;
+ wave = 1;
+ speed_bonus = 0;
+
+ // Initialize arrays
+ for (int i = 0; i < MAX_BULLETS; i++) {
+ bullet_active[i] = false;
+ bullet_x[i] = 0;
+ bullet_y[i] = 0;
+ }
+
+ for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
+ enemy_bullet_active[i] = false;
+ enemy_bullet_x[i] = 0;
+ enemy_bullet_y[i] = 0;
+ }
+
+ init_invaders();
+ }
+}
+
+void SpaceInvadersView::frame_sync() {
+ check_game_timer();
+ set_dirty();
+}
+
+bool SpaceInvadersView::on_encoder(const EncoderEvent delta) {
+ if (game_state == 1) {
+ if (delta > 0) {
+ player_x += 5;
+ if (player_x > 214) player_x = 214;
+ draw_player();
+ } else if (delta < 0) {
+ player_x -= 5;
+ if (player_x < 0) player_x = 0;
+ draw_player();
+ }
+
+ set_dirty();
+ }
+ return true;
+}
+
+bool SpaceInvadersView::on_key(const KeyEvent key) {
+ if (key == KeyEvent::Select) {
+ if (game_state == 0) {
+ // Start game
+ game_state = 1;
+ button_difficulty.hidden(true);
+ score = 0;
+ lives = 3;
+ wave = 1;
+ speed_bonus = 0;
+ invader_move_delay = 30;
+ enemy_fire_counter = 0;
+ init_invaders();
+
+ painter.fill_rectangle({0, 0, 240, 320}, Color::black());
+ painter.draw_hline({0, 49}, 240, Color::white());
+
+ auto style = *ui::Theme::getInstance()->fg_green;
+ painter.draw_string({5, 10}, style, "Score: 0");
+ painter.draw_string({5, 30}, style, "Lives: 3");
+
+ draw_all_invaders();
+ draw_player();
+
+ set_dirty();
+ } else if (game_state == 1) {
+ fire_bullet();
+ } else if (game_state == 2) {
+ // Return to menu
+ game_state = 0;
+ menu_initialized = false;
+ game_over_initialized = false;
+ button_difficulty.hidden(false);
+ set_dirty();
+ }
+ }
+
+ return true;
+}
+
+} // namespace ui::external_app::spaceinv
diff --git a/firmware/application/external/spaceinv/ui_spaceinv.hpp b/firmware/application/external/spaceinv/ui_spaceinv.hpp
new file mode 100644
index 000000000..0aee4c0c9
--- /dev/null
+++ b/firmware/application/external/spaceinv/ui_spaceinv.hpp
@@ -0,0 +1,79 @@
+/*
+ * ------------------------------------------------------------
+ * | Made by RocketGod |
+ * | Find me at https://betaskynet.com |
+ * | Argh matey! |
+ * ------------------------------------------------------------
+ */
+
+#ifndef __UI_SPACEINV_H__
+#define __UI_SPACEINV_H__
+
+#include "ui.hpp"
+#include "ui_navigation.hpp"
+#include "event_m0.hpp"
+#include "message.hpp"
+#include "irq_controls.hpp"
+#include "random.hpp"
+#include "lpc43xx_cpp.hpp"
+#include "ui_widget.hpp"
+#include "app_settings.hpp"
+
+namespace ui::external_app::spaceinv {
+
+using Callback = void (*)(void);
+
+class Ticker {
+ public:
+ Ticker() = default;
+ void attach(Callback func, double delay_sec);
+ void detach();
+};
+
+void check_game_timer();
+void game_timer_check();
+
+class SpaceInvadersView : public View {
+ public:
+ SpaceInvadersView(NavigationView& nav);
+ ~SpaceInvadersView(); // Destructor will trigger settings save
+ void on_show() override;
+
+ std::string title() const override { return "Space Invaders"; }
+
+ void focus() override { dummy.focus(); }
+ void paint(Painter& painter) override;
+ void frame_sync();
+ bool on_encoder(const EncoderEvent event) override;
+ bool on_key(KeyEvent key) override;
+
+ uint32_t highScore = 0;
+ bool easy_mode = false;
+
+ private:
+ NavigationView& nav_;
+
+ Button button_difficulty{
+ {70, 285, 100, 20},
+ "Mode: HARD"};
+
+ app_settings::SettingsManager settings_{
+ "spaceinv",
+ app_settings::Mode::NO_RF,
+ {{"highscore"sv, &highScore},
+ {"easy_mode"sv, &easy_mode}}};
+
+ Button dummy{
+ {240, 0, 0, 0},
+ ""};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->frame_sync();
+ }};
+};
+
+} // namespace ui::external_app::spaceinv
+
+#endif /* __UI_SPACEINV_H__ */
diff --git a/firmware/application/external/spainter/ui_spectrum_painter.cpp b/firmware/application/external/spainter/ui_spectrum_painter.cpp
index 31cb4ce67..90b13c7da 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter.cpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter.cpp
@@ -55,7 +55,7 @@ SpectrumPainterView::SpectrumPainterView(
&field_pause,
});
- Rect view_rect = {0, 3 * 8, 240, 80};
+ Rect view_rect = {0, 3 * 8, screen_width, 80};
input_image.set_parent_rect(view_rect);
input_text.set_parent_rect(view_rect);
diff --git a/firmware/application/external/spainter/ui_spectrum_painter.hpp b/firmware/application/external/spainter/ui_spectrum_painter.hpp
index 63e7e9c9b..4efde91ed 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter.hpp
@@ -89,7 +89,7 @@ class SpectrumPainterView : public View {
static constexpr int32_t footer_location = 15 * 16 + 8;
ProgressBar progressbar{
- {4, footer_location - 16, 240 - 8, 16}};
+ {4, footer_location - 16, screen_width - 8, 16}};
Labels labels{
{{10 * 8, footer_location + 1 * 16}, "GAIN A:", Theme::getInstance()->fg_light->foreground},
@@ -121,7 +121,7 @@ class SpectrumPainterView : public View {
true};
ImageButton button_play{
- {28 * 8, footer_location + 1 * 16, 2 * 8, 1 * 16},
+ {screen_width - 2 * 8, footer_location + 1 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_image.cpp b/firmware/application/external/spainter/ui_spectrum_painter_image.cpp
index fd2e8cb18..c47c522ab 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_image.cpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_image.cpp
@@ -244,12 +244,12 @@ std::vector SpectrumInputImageView::get_line(uint16_t y) {
void SpectrumInputImageView::paint(Painter& painter) {
painter.fill_rectangle(
- {{0, 40}, {240, 204}},
+ {{0, 40}, {screen_width, 204}},
style().background);
if (!painted) {
// This is very slow for big pictures. Do only once.
- this->drawBMP_scaled({{0, 40}, {240, 160}}, this->file);
+ this->drawBMP_scaled({{0, 40}, {screen_width, 160}}, this->file);
painted = true;
}
}
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_image.hpp b/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
index a9c1bf622..2b0e2ee64 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
@@ -58,7 +58,7 @@ class SpectrumInputImageView : public View {
uint32_t data_start{0};
Button button_load_image{
- {0 * 8, 11 * 16 - 4, 30 * 8, 28},
+ {0 * 8, 11 * 16 - 4, screen_width, 28},
"Load Image ..."};
bool drawBMP_scaled(const ui::Rect r, const std::string file);
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_text.cpp b/firmware/application/external/spainter/ui_spectrum_painter_text.cpp
index 8d2710977..a9cef177e 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_text.cpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_text.cpp
@@ -56,7 +56,7 @@ SpectrumInputTextView::~SpectrumInputTextView() {
}
void SpectrumInputTextView::on_set_text(NavigationView& nav) {
- text_prompt(nav, buffer, 300);
+ text_prompt(nav, buffer, 300, ENTER_KEYBOARD_MODE_DIGITS);
}
void SpectrumInputTextView::focus() {
@@ -73,7 +73,7 @@ void SpectrumInputTextView::paint(Painter& painter) {
}
painter.fill_rectangle(
- {{0, 40}, {240, 204}},
+ {{0, 40}, {screen_width, 204}},
style().background);
}
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_text.hpp b/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
index f31305d2f..f4c91a2f3 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
@@ -53,43 +53,43 @@ class SpectrumInputTextView : public View {
void on_set_text(NavigationView& nav);
Text text_message_0{
- {0 * 8, 0 * 16, 30 * 8, 16},
+ {0 * 8, 0 * 16, screen_width, 16},
""};
Text text_message_1{
- {0 * 8, 1 * 16, 30 * 8, 16},
+ {0 * 8, 1 * 16, screen_width, 16},
""};
Text text_message_2{
- {0 * 8, 2 * 16, 30 * 8, 16},
+ {0 * 8, 2 * 16, screen_width, 16},
""};
Text text_message_3{
- {0 * 8, 3 * 16, 30 * 8, 16},
+ {0 * 8, 3 * 16, screen_width, 16},
""};
Text text_message_4{
- {0 * 8, 4 * 16, 30 * 8, 16},
+ {0 * 8, 4 * 16, screen_width, 16},
""};
Text text_message_5{
- {0 * 8, 5 * 16, 30 * 8, 16},
+ {0 * 8, 5 * 16, screen_width, 16},
""};
Text text_message_6{
- {0 * 8, 6 * 16, 30 * 8, 16},
+ {0 * 8, 6 * 16, screen_width, 16},
""};
Text text_message_7{
- {0 * 8, 7 * 16, 30 * 8, 16},
+ {0 * 8, 7 * 16, screen_width, 16},
""};
Text text_message_8{
- {0 * 8, 8 * 16, 30 * 8, 16},
+ {0 * 8, 8 * 16, screen_width, 16},
""};
Text text_message_9{
- {0 * 8, 9 * 16, 30 * 8, 16},
+ {0 * 8, 9 * 16, screen_width, 16},
""};
std::array text_message{{
@@ -106,7 +106,7 @@ class SpectrumInputTextView : public View {
}};
Button button_message{
- {0 * 8, 11 * 16 - 4, 30 * 8, 28},
+ {0 * 8, 11 * 16 - 4, screen_width, 28},
"Set message"};
};
diff --git a/firmware/application/external/stopwatch/main.cpp b/firmware/application/external/stopwatch/main.cpp
new file mode 100644
index 000000000..1bfea6228
--- /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 000000000..112dc8740
--- /dev/null
+++ b/firmware/application/external/stopwatch/ui_stopwatch.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2025 Mark Thompson
+ * copyleft Mr. Robot of F.Society
+ *
+ * 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 {
+
+// clang-format off
+// clang-format doesn't allow use as this way but it's not worth to have const var for this thing. too expensive
+
+// minute
+#define TOTAL_M1_POS {0 * 8 * 4, 3 * 16}
+#define TOTAL_M2_POS {1 * 8 * 4, 3 * 16}
+
+// sec
+#define TOTAL_S1_POS {2 * 8 * 4 + 24, 3 * 16}
+#define TOTAL_S2_POS {3 * 8 * 4 + 24, 3 * 16}
+
+// ms
+#define TOTAL_MS1_POS {4 * 8 * 4 + 7 * 8, 3 * 16 + 24}
+#define TOTAL_MS2_POS {5 * 8 * 4 + 5 * 8, 3 * 16 + 24}
+#define TOTAL_MS3_POS {6 * 8 * 4 + 3 * 8, 3 * 16 + 24}
+
+#define LAP_Y 9 * 16
+
+//lap min
+#define LAP_M1_POS {0 * 8 * 4, LAP_Y}
+#define LAP_M2_POS {1 * 8 * 4, LAP_Y}
+
+//lap sec
+#define LAP_S1_POS {2 * 8 * 4 + 24, LAP_Y}
+#define LAP_S2_POS {3 * 8 * 4 + 24, LAP_Y}
+
+//lap ms
+#define LAP_MS1_POS {4 * 8 * 4 + 7 * 8, LAP_Y + 24}
+#define LAP_MS2_POS {5 * 8 * 4 + 5 * 8, LAP_Y + 24}
+#define LAP_MS3_POS {6 * 8 * 4 + 3 * 8, LAP_Y + 24}
+// clang-format on
+
+StopwatchView::StopwatchView(NavigationView& nav)
+ : painter{} {
+ add_children({
+ &labels,
+ &button_run_stop,
+ &button_reset_lap,
+ &button_done,
+ &options_ms_display_level,
+ });
+
+ 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();
+ };
+
+ options_ms_display_level.on_change = [&](size_t, ui::OptionsField::value_t) {
+ clean_ms_display(options_ms_display_level.selected_index());
+ };
+
+ options_ms_display_level.set_selected_index(0);
+
+ refresh_painting();
+}
+
+void StopwatchView::focus() {
+ button_run_stop.focus();
+}
+
+void StopwatchView::run() {
+ options_ms_display_level.hidden(true);
+ // ^ this won't take efferts if don't set_dirty, but needed to let user can't change during ticking
+ if (!paused) refresh_painting();
+ paused = false;
+ running = true;
+ start_time = chTimeNow() - previously_elapsed;
+ button_run_stop.set_text("STOP");
+ button_reset_lap.set_text("LAP");
+}
+
+void StopwatchView::stop() {
+ options_ms_display_level.hidden(false);
+ running = false;
+ paused = true;
+ 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;
+ for (uint8_t i = 0; i < 7; i++) {
+ refresh_painting();
+ }
+ refresh_painting();
+ set_dirty();
+}
+
+void StopwatchView::lap() {
+ lap_time = chTimeNow();
+}
+
+void StopwatchView::paint(Painter&) {
+}
+
+void StopwatchView::refresh_painting() {
+ // minute
+ painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
+ painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
+
+ // sec
+ painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
+ painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
+
+ // ms
+ painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
+
+ // lap min
+ painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
+ painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
+
+ // lap sec
+ painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
+ painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
+
+ // lap ms
+ painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
+}
+
+/*when user seted it to display more, then display less later, this prevent the remain value exist on screen*/
+void StopwatchView::clean_ms_display(uint8_t level) {
+ level++;
+ switch (level) {
+ case 0:
+ painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ break;
+ case 1:
+ painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ break;
+ case 2:
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
+ break;
+ }
+}
+
+void StopwatchView::resume_last() {
+ // minute
+ painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, last_displayed[0], 4);
+ painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, last_displayed[1], 4);
+
+ // sec
+ painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, last_displayed[2], 4);
+ painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, last_displayed[3], 4);
+
+ // ms
+ painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, last_displayed[4], 2);
+ painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, last_displayed[5], 2);
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, last_displayed[6], 2);
+}
+
+/* NB:
+ * Due to the flaw of dirty management, it's using work around, to reduce screen flickering:
+ *
+ * for example when xx:15:xxx turn to xx:16:xxx, it actually only paint 6, the 1 is old,
+ * but not dirty, so we can see without flikering.
+ *
+ * So with these work around, it won't show false info, but bare in mind that it could be, if you add more things.
+ */
+void StopwatchView::frame_sync() {
+ uint32_t elapsed_ticks = 0;
+
+ if (running) {
+ end_time = chTimeNow();
+ elapsed_ticks = end_time - start_time;
+ } else if (previously_elapsed > 0) {
+ elapsed_ticks = previously_elapsed;
+ }
+
+ constexpr uint32_t TICKS_PER_SECOND = CH_FREQUENCY;
+ constexpr uint32_t TICKS_PER_MINUTE = TICKS_PER_SECOND * 60;
+
+ uint32_t minutes = elapsed_ticks / TICKS_PER_MINUTE;
+ uint32_t seconds = (elapsed_ticks % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
+ uint32_t milliseconds = ((elapsed_ticks % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
+
+ // minute
+ if (last_displayed[0] != (minutes / 10) % 10)
+ painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_red, '0' + (minutes / 10) % 10, 4);
+ if (last_displayed[1] != minutes % 10)
+ painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_red, '0' + minutes % 10, 4);
+
+ // sec
+ if (last_displayed[2] != (seconds / 10) % 10)
+ painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_green, '0' + (seconds / 10) % 10, 4);
+ if (last_displayed[3] != seconds % 10)
+ painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_green, '0' + seconds % 10, 4);
+
+ // ms
+ /* v place holder to aligh logic*/
+ if ((true) && last_displayed[4] != (milliseconds / 100) % 10)
+ painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 100) % 10, 2);
+ if ((options_ms_display_level.selected_index() >= 1) && last_displayed[5] != (milliseconds / 10) % 10)
+ painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 10) % 10, 2);
+ if ((options_ms_display_level.selected_index() >= 2) && last_displayed[6] != milliseconds % 10)
+ painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_yellow, '0' + milliseconds % 10, 2);
+
+ // min
+ last_displayed[0] = (minutes / 10) % 10;
+ last_displayed[1] = minutes % 10;
+ // sec
+ last_displayed[2] = (seconds / 10) % 10;
+ last_displayed[3] = seconds % 10;
+ // ms
+ last_displayed[4] = (milliseconds / 100) % 10;
+ last_displayed[5] = (milliseconds / 10) % 10;
+ last_displayed[6] = milliseconds % 10;
+
+ if (lap_time > 0) {
+ uint32_t lap_elapsed = lap_time - start_time;
+
+ uint32_t lap_minutes = lap_elapsed / TICKS_PER_MINUTE;
+ uint32_t lap_seconds = (lap_elapsed % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
+ uint32_t lap_milliseconds = ((lap_elapsed % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
+
+ // lap min
+ if (lap_last_displayed[0] == (lap_minutes / 10) % 10) {
+ painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, '0' + (lap_minutes / 10) % 10, 4);
+ }
+ if (lap_last_displayed[1] == lap_minutes % 10) {
+ painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, '0' + lap_minutes % 10, 4);
+ }
+
+ // lap sec
+ if (lap_last_displayed[2] == (lap_seconds / 10) % 10) {
+ painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, '0' + (lap_seconds / 10) % 10, 4);
+ }
+ if (lap_last_displayed[3] == lap_seconds % 10) {
+ painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, '0' + lap_seconds % 10, 4);
+ }
+
+ // lap ms
+ if (lap_last_displayed[4] == (lap_milliseconds / 100) % 10) {
+ painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 100) % 10, 2);
+ }
+ if (lap_last_displayed[5] == (lap_milliseconds / 10) % 10) {
+ painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 10) % 10, 2);
+ }
+ if (lap_last_displayed[6] == lap_milliseconds % 10) {
+ painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, '0' + lap_milliseconds % 10, 2);
+ }
+
+ // lp m
+ lap_last_displayed[0] = (lap_minutes / 10) % 10;
+ lap_last_displayed[1] = lap_minutes % 10;
+
+ // lp s
+ lap_last_displayed[2] = (lap_seconds / 10) % 10;
+ lap_last_displayed[3] = lap_seconds % 10;
+
+ // lp mss
+ lap_last_displayed[4] = (lap_milliseconds / 100) % 10;
+ lap_last_displayed[5] = (lap_milliseconds / 10) % 10;
+ lap_last_displayed[6] = lap_milliseconds % 10;
+ }
+}
+
+} // 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 000000000..fa44b24c7
--- /dev/null
+++ b/firmware/application/external/stopwatch/ui_stopwatch.hpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2025 Mark Thompson
+ * copyleft Mr. Robot of F.Society
+ *
+ * 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();
+ void cover_display_area_with_0();
+ void refresh_painting();
+ void resume_last();
+ void clean_ms_display(uint8_t level = 0);
+
+ bool running{false};
+ bool paused{false};
+ long long start_time{0};
+ long long end_time{0};
+ long long lap_time{0};
+ long long previously_elapsed{0};
+ uint8_t last_displayed[7] = {0, 0, 0, 0, 0, 0, 0};
+ /* m m s s ms ms ms*/
+ uint8_t lap_last_displayed[7] = {0, 0, 0, 0, 0, 0, 0};
+ /* m m s s ms ms ms*/
+
+ Labels labels{
+ {{0 * 8, 1 * 16}, "TOTAL:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 7 * 16}, "LAP:", Theme::getInstance()->fg_light->foreground},
+ };
+
+ OptionsField options_ms_display_level{
+ {4 * 8 * 4 + 7 * 8 + 4, 2 * 16},
+ 5,
+ {{"& - -", 0},
+ {"& & -", 1},
+ {"& & &", 2}}};
+
+ Painter painter;
+
+ Button button_run_stop{
+ {72, 210, 96, 24},
+ "START"};
+
+ Button button_reset_lap{
+ {72, screen_height - 80, 96, 24},
+ "RESET"};
+
+ Button button_done{
+ {72, screen_height - 50, 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/application/external/tetris/SPI_TFT_ILI9341.h b/firmware/application/external/tetris/SPI_TFT_ILI9341.h
deleted file mode 100644
index 8f727f4ef..000000000
--- a/firmware/application/external/tetris/SPI_TFT_ILI9341.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-// "HAL" display layer for Tetris code to run on PortaPack without its original ILI9341 functions
-
-#ifndef __UI_SPI_TFT_ILI9341_H__
-#define __UI_SPI_TFT_ILI9341_H__
-
-ui::Painter painter;
-
-static int x_pos{0};
-static int y_pos{0};
-static int fg_color;
-static int bg_color;
-
-enum {
- White,
- Blue,
- Yellow,
- Purple,
- Green,
- Red,
- Maroon,
- Orange,
- Black,
-};
-
-// pp_colors must be in same order as enums above
-static const Color pp_colors[] = {
- Color::white(),
- Color::blue(),
- Color::yellow(),
- Color::purple(),
- Color::green(),
- Color::red(),
- Color::magenta(),
- Color::orange(),
- Color::black(),
-};
-
-// NB: ELIMINATED SPI_TFT_ILI9341 DISPLAY CLASS DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS
-
-static void claim(__FILE* x) {
- (void)x;
-};
-
-static void cls() {
- painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
-};
-
-static void background(int color) {
- bg_color = color;
-};
-
-static void foreground(int color) {
- fg_color = color;
-};
-
-static void locate(int x, int y) {
- x_pos = x;
- y_pos = y;
-};
-
-static void set_orientation(int x) {
- (void)x;
-};
-
-static void set_font(unsigned char* x) {
- (void)x;
-};
-
-static void fillrect(int x1, int y1, int x2, int y2, int color) {
- painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
-};
-
-static void rect(int x1, int y1, int x2, int y2, int color) {
- painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
-};
-
-static void printf(std::string str) {
- auto style = (fg_color == White) ? *ui::Theme::getInstance()->bg_darkest : *ui::Theme::getInstance()->bg_lightest;
- painter.draw_string({x_pos, y_pos - 1}, style, str);
-};
-
-static void printf(std::string str, int v) {
- if (str.find_first_of("%") != std::string::npos) {
- str.resize(str.find_first_of("%")); // remove %d from end of string
- }
- printf(str + to_string_dec_uint(v));
-};
-
-#endif /*__UI_SPI_TFT_ILI9341_H__*/
diff --git a/firmware/application/external/tetris/main.cpp b/firmware/application/external/tetris/main.cpp
index fe66298a1..2a172b951 100644
--- a/firmware/application/external/tetris/main.cpp
+++ b/firmware/application/external/tetris/main.cpp
@@ -73,8 +73,8 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
0xFF,
0xF1,
},
- /*.icon_color = */ ui::Color::orange().v,
- /*.menu_location = */ app_location_t::UTILITIES,
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
diff --git a/firmware/application/external/tetris/mbed.h b/firmware/application/external/tetris/mbed.h
deleted file mode 100644
index c74b7b048..000000000
--- a/firmware/application/external/tetris/mbed.h
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-// "HAL" layer for Tetris code to run on PortaPack without its original mbed OS
-// (the dream here was to avoid modifying the original code)
-
-#ifndef __UI_mbed_H__
-#define __UI_mbed_H__
-
-using Callback = void (*)(void);
-
-#define wait_us(x) (void)0
-#define wait(x) chThdSleepMilliseconds(x * 1000)
-#define PullUp 1
-
-enum {
- dp0,
- dp1,
- dp2,
- dp3,
- dp4,
- dp5,
- dp6,
- dp7,
- dp8,
- dp9,
- dp10,
- dp11,
- dp12,
- dp13,
- dp14,
- dp15,
- dp16,
- dp17,
- dp18,
- dp19,
- dp20,
- dp21,
- dp22,
- dp23,
- dp24,
- dp25,
-};
-
-static bool but_RIGHT;
-static bool but_LEFT;
-static bool but_UP;
-static bool but_DOWN;
-static bool but_SELECT;
-
-//
-// AnalogIn Class -- DID NOT WORK DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS -- hacked original code module instead
-//
-// dp9 = joystick rotate button --> select button
-// dp10 = joystick y --> up & down buttons
-// dp11 = joystick x --> left & right buttons
-// dp13 = random number generator
-//
-//
-// class AnalogIn {
-// public:
-// AnalogIn(uint32_t analog_input) {
-// // FIXME - THIS CODE NEVER GETS EXECUTED!
-// analog_input_ = analog_input;
-// };
-//
-// // Tetris code only uses this function for dp13 - supposed to be a random number
-// uint16_t read_u16() {
-// return std::rand();
-// };
-//
-// // Tetris code uses read() function for direction buttons only
-// float read() {
-// float retval = 0.5;
-// switch (analog_input_) {
-// case dp11:
-// if (but_LEFT)
-// retval = 0.0;
-// else if (but_RIGHT)
-// retval = 1.0;
-// break;
-//
-// case dp10:
-// if (but_UP)
-// retval = 0.0;
-// else if (but_DOWN)
-// retval = 1.0;
-// break;
-// }
-// return retval;
-// };
-//
-// operator float() {
-// return read();
-// };
-//
-// private:
-// uint32_t analog_input_{INT_MAX};
-// };
-
-//
-// Timer Class
-// (Timer object was used for unneeded button debouncing, so just returning 1000ms to indicate we've waited long enough)
-//
-class Timer {
- public:
- // NOTE: INITIALIZER CODE WON'T RUN
- Timer() { (void)0; };
- void reset() { (void)0; };
- void start() { (void)0; }
- uint32_t read_ms() { return 1000; };
-
- private:
-};
-
-//
-// Ticker Class
-// (Ticker is timed callback, used for checking "joystick" directional switches and when time to move piece down a row)
-//
-// NB: Only one callback is supported per Ticker class instantiation
-static Callback fall_timer_callback;
-static uint32_t fall_timer_timeout;
-static uint32_t fall_timer_counter;
-
-static Callback dir_button_callback;
-
-static void check_fall_timer() {
- if (fall_timer_callback) {
- if (++fall_timer_counter >= fall_timer_timeout) {
- fall_timer_counter = 0;
- fall_timer_callback();
- }
- }
-}
-
-class Ticker {
- public:
- // NOTE: INITIALIZER CODE WON'T RUN
- Ticker() { (void)0; };
-
- void attach(Callback func, double delay_sec) {
- // 0.3 sec is requested only for button check -- kludge to use on_key callback for this one instead of timer
- if (delay_sec == 0.3) {
- dir_button_callback = func;
- } else {
- fall_timer_callback = func;
- fall_timer_timeout = delay_sec * 60; // timer interrupts at 60 Hz
- }
- }
-
- void detach() {
- // shouldn't detach both, but don't know how to tell which object is which
- dir_button_callback = nullptr;
- fall_timer_callback = nullptr;
- }
-
- private:
-};
-
-//
-// InterruptIn Class
-// (just used for the Select button)
-//
-static Callback sel_button_callback;
-
-static bool check_encoder(const EncoderEvent delta) {
- (void)delta;
- // TODO: consider adding ability to rotate Tetronimo via encoder too
- return false;
-}
-
-static bool check_key(const KeyEvent key) {
- auto switches_debounced = get_switches_state().to_ulong();
- but_RIGHT = (switches_debounced & 0x01) != 0;
- but_LEFT = (switches_debounced & 0x02) != 0;
- but_DOWN = (switches_debounced & 0x04) != 0;
- but_UP = (switches_debounced & 0x08) != 0;
- but_SELECT = (switches_debounced & 0x10) != 0;
-
- if (key == KeyEvent::Select) {
- if (sel_button_callback)
- sel_button_callback();
- } else {
- if (dir_button_callback)
- dir_button_callback();
- }
- return true;
-}
-
-class InterruptIn {
- public:
- InterruptIn(int reg) {
- // NOTE: INITIALIZER CODE WON'T RUN
- (void)reg;
- };
- void fall(Callback func) { sel_button_callback = func; };
- void mode(int v) { (void)v; };
-
- private:
-};
-
-#endif /*__UI_mbed_H__*/
diff --git a/firmware/application/external/tetris/tetris.cpp b/firmware/application/external/tetris/tetris.cpp
deleted file mode 100644
index c6e2fb957..000000000
--- a/firmware/application/external/tetris/tetris.cpp
+++ /dev/null
@@ -1,548 +0,0 @@
-// Projekat Tetris
-// by Vrnjak Lamija & Selimović Denis
-// Elektrotehnički fakultet Sarajevo
-
-// clang-format off
-
-//////// HACKED FOR PORTAPACK -- CHANGES HIGHLIGHTED
-int main();
-void pause_game();
-void Initialize(unsigned char c);
-void DeleteFigure();
-void DrawFigure();
-bool InCollisionDown(char delta);
-bool InCollisionLeft();
-bool InCollisionRight();
-//////// PORTAPACK
-
-#include "mbed.h"
-#include "SPI_TFT_ILI9341.h"
-#include "Arial12x12.h"
-
-#define dp23 P0_0
-
-//////// PORTAPACK - DISABLED MOST CLASSES DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS:
-//deklaracija display-a
-//SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT");
-//
-//analogni ulazi za joystick
-// AnalogIn VRx(dp11);
-// AnalogIn VRy(dp10);
-//
-// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti
-//////// PORTAPACK
-
-//taster na joysticku za rotaciju
-InterruptIn taster(dp9);
-
-//ticker za spustanje figure
-//timer za debouncing tastera na joysticku
-Ticker game, joystick;
-Timer debounceTaster;
-
-unsigned char level = 0; //mora biti tipa usigned char jer inače se može desiti da level bude manji od 0, a i da ne trošimo memoriju
-const float delays[4] = {1.2, 0.7, 0.4, 0.25}; //svakih koliko se spusti jedan red, ovo provjeriti da li je presporo ili prebrzo, ovisi o levelu
-
-//////// PORTAPACK - DELETED UNNEEDED JOYSTICK HYSTERESIS VARIABLES
-//char leftBoundary = 1, rightBoundary = 5, downBoundary = 1, upBoundary = 5;// sada je ovo tipa char
-//////// PORTAPACK
-
-unsigned int score = 0; //stavio sam ovo unsigned int za veći opseg, mada je jako teško da se i int premaši, ali nmvz
-bool firstTime = true; //ako je prvi put, figura se crta u Tickeru
-bool gameStarted = false;
-unsigned char nextFigure = 1; //ovo je sad globalna varijabla, da bi mogli unaprijed generisati sljedeću figuru radi prikaza
-
-//white - no figure
-//I - BLUE
-//O - YELLOW
-//T - PURPLE
-//S - GREEN
-//Z - RED
-//J - MAROON
-//L - ORANGE
-const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
-const short DIMENSION = 16;
-const short DIMENSION_NEXT = 12;
-short board[20][10] = {0}; //matrica boja, 0 - 7 indeksi boja
-
-short figuresX[7][4] = {{0,0,0,0}, {0,0,1,1}, {0,1,1,1}, {1,1,0,0}, {0,1,0,1}, {1,1,1,0}, {1,1,1,0}};
-short figuresY[7][4] = {{0,1,2,3}, {1,0,0,1}, {1,1,2,0}, {0,1,1,2}, {0,1,1,2}, {2,1,0,0}, {0,1,2,2}};
-
-unsigned int GenerateRandomSeed() {
-//////// PORTAPACK - USE RTC FOR SEED
-return LPC_RTC->CTIME0;
-//////// PORTAPACK
-}
-
-void Init() {
- //ovo su zajedničke osobine za sve prikaze na display-u
- //nikad se ne mijenjaju i pozvat ćemo je jednom prije petlje
- claim(stdout);
- set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat
- set_font((unsigned char*) Arial12x12);
-}
-
-
-void ShowScore() {
- //pomocna funkcija za prikazivanje score-a
- fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score
- locate(200, 35); //valjda je na sredini pravougaonika
- printf("%d", score);
-}
-
-void ShowNextFigure() {
- //prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure
- fillrect(165, 70, 235, 120, White);
- int upperLeftX = 176, upperLeftY = 83;
- for(int i = 0; i < 4; i++) {
- int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
- fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
- rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
- }
-}
-
-//funkcija za crtanje cursora za odabir levela
-void DrawCursor(int color, unsigned char lev) {
- fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
-}
-
-// PORTAPACK - ADDED EXTRA LEVEL:
-void ShowLevelMenu() {
- //ovdje inicijalizujemo display
- cls(); // brišemo prethodno
- background(Black);
- foreground(White);
- locate(80, 50);
- printf("LEVEL 1");
- locate(80, 120);
- printf("LEVEL 2");
- locate(80, 190);
- printf("LEVEL 3");
- locate(80, 260);
- printf("LEVEL 4");
- DrawCursor(White, level);
-}
-//////// PORTAPACK
-
-//////// PORTAPACK - USE BUTTONS VS JOYSTICK:
-void ReadJoystickForLevel(){
- unsigned char old = level;
- if(but_UP){
- (level == 0) ? level = 3 : level--;
- }
- else if(but_DOWN){
- //ne radi ona prethodna varijanta jer % vraća i negastivni rezultat
- //to što ne koristimo unsigned tip ne pomaže jer će doći do overflow-a
- level = (level + 1) % 4;
- }
- DrawCursor(Black, old); //na prethodni level popunimo bojom pozadine
- DrawCursor(White, level); //na novi level popunimo bijelom bojom - pozadina je crna
- //koristio sam fillrect, jer njega svakako moramo koristiti, jer možda budemo morali da brišemo fillcircle iz biblioteke
-}
-//////// PORTAPACK
-
-void EndPlay() {
- joystick.detach();
- score = 0;
- firstTime = true;
- gameStarted = false;
- for(int i = 0; i < 20; i++) {
- for(int j = 0; j < 10; j++) {
- board[i][j] = 0;
- }
- }
-//////// PORTAPACK - FIX TO REINITIALIZE SCREEN
-// ShowLevelMenu();
-// joystick.attach(&ReadJoystickForLevel, 0.3);
-main();
-//////// PORTAPACK
-}
-
-void StartGame()
-{
- cls(); // brišemo ShowLevelMenu
- background(White);
- foreground(Black);
- fillrect(0, 0, 160, 320, White);
- fillrect(160, 0, 240, 320, Black); //dio za prikazivanje rezultata će biti crni pravougaonik, a tabla je bijeli
- ShowScore();
-}
-
-void copyCoordinates(short X[], short Y[], unsigned char index)
-{
- //umjesto one prošle metode za kopiranje, ova prima index, čisto da nam olakša život
- for(int i = 0; i < 4; i++) {
- X[i] = figuresX[index][i];
- Y[i] = figuresY[index][i];
- }
-}
-
-bool BottomEdge(int x){
- return x > 19;
-}
-
-bool LeftEdge(int y){
- return y < 0;
-}
-
-bool RightEdge(int y){
- return y > 9;
-}
-
-bool OutOfBounds(int y, int x){
- return y < 0 || y > 9 || x > 19;
-}
-
-void PutBorders(short x, short y) {
- for(int i = x - 1; i <= x + 1; i++) {
- for(int j = y - 1; j <= y + 1; j++) {
- if(i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
- rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
- }
- }
-}
-
-//////// PORTAPACK - ELIMINATED CLASSES DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS:
-//class Tetromino{
-//private:
- short X[4];
- short Y[4];
- short boardX, boardY;
- unsigned char colorIndex;//dodao sam colorIndex zasad, jer nema drugog načina da popunimo matricu sa indeksima boja
- //ovo je najbezbolnija varijanta što se memorije tiče
-//public:
-// Tetromino(){
-// unsigned char r = rand() % 7 + 1;
-// Initialize(r);
-// }
-
- void Tetromino(unsigned char c) {
- Initialize(c);
- }
-
- void Initialize(unsigned char c) {
- colorIndex = c;
- boardX = 0;
- boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display
- copyCoordinates(X, Y, c - 1);
- }
-
- void Rotate(){
- short pivotX = X[1], pivotY = Y[1];
- //prvi elemnti u matrici su pivoti oko koje rotiramo
-
- short newX[4]; //pozicije blokova nakon rotiranja ako sve bude ok
- short newY[4];
-
- for(int i = 0; i < 4; i++){
- short tmp = X[i], tmp2 = Y[i];
- newX[i] = pivotX + pivotY - tmp2;
- newY[i] = tmp + pivotX - pivotY;
-
- if(OutOfBounds(boardY + newY[i], boardX + newX[i]) || board[boardX + newX[i]][boardY + newY[i]] != 0) return;
- }
- DeleteFigure();
- for(int i = 0; i < 4; i++){
- X[i] = newX[i];
- Y[i] = newY[i];
- }
- DrawFigure();
- }
-
- void DrawFigure() {
- for(int i = 0; i < 4; i++) {
- //display je deklarisani display logy iz biblioteke one
- //računamo gornji lijevi pixel po x i y
- //donji desni dobijemo kad dodamo DIMENZIJU
- //stavio sam 16 za početak, možemo se opet skontati na labu
- //ovo pretpostavlja da nema margina, mogu se lagano dodati uz neku konstantu kao offset
- int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
- fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
- //ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa
- rect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
- }
- }
-
- void DeleteFigure() {
- for(int i = 0; i < 4; i++) {
- //ista logika kao u DrawFigure, samo popunjavamo sve blokove sa bijelim pravougaonicima
- short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
- fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, White);
- PutBorders(upperLeftY, upperLeftX);
- }
- }
-
- void OnAttached() {
- //metoda se poziva kad figura prestanje da se krece
- //popunimo matricu indeksom boje
- for(int i = 0; i < 4; i++) {
- board[boardX + X[i]][boardY + Y[i]] = colorIndex;
- //btw board bi mogao biti niz tipa unsigned char, ali to ćemo vidjet kasnije
- }
- }
-
- bool MoveDown(char delta = 1){
- if(!InCollisionDown(delta)){
- DeleteFigure();
- boardX+=delta;
- DrawFigure();
- return true;
- }
-
- return false;
- }
-
- void MoveLeft(){
- if(!InCollisionLeft()){
- DeleteFigure();
- boardY--;
- DrawFigure();
- }
- }
-
- void MoveRight(){
- if(!InCollisionRight()){
- DeleteFigure();
- boardY++;
- DrawFigure();
- }
- }
-
- void SoftDrop() {
- //ovo je funkcija za soft drop
- //obrisemo figuru, postavimo novu poziciju dva reda ispod, nacrtamo figuru
- DeleteFigure();
- MoveDown(2);
- DrawFigure();
- //treba još vidjeti koje izmjene u tickeru trebaju
- score += 2 * (level +1); //prema igrici koju smo igrali, dobije se 14 poena kad se uradi hard drop
- ShowScore();
- }
-
-
- bool InCollisionDown(char delta = 1){
- int newX, newY; //da bi bilo citljivije
-
- for(int i = 0; i < 4; i++){
- newX = boardX + X[i] + delta;
- newY = boardY + Y[i];
-
- if(BottomEdge(newX) || board[newX][newY] != 0){
- return true;
- }
- //jedna figura je u koliziji ako
- //pozicija na koju zelimo da pomjerimo bilo koji blok dotakne dno ili lijevu ili desnu ivicu ekrana
- //ili ako je pozicija na koju zelimo da pomjerimo bilo koji od blokova vec zauzeta a da nije dio figure prije pomijeranja
- }
-
- return false;
- }
-
- bool InCollisionLeft(){
- int newX, newY;
-
- for(int i = 0; i < 4; i++){
- newX = boardX + X[i];
- newY = boardY + Y[i] - 1;
-
- if(LeftEdge(newY) || board[newX][newY] != 0){
- return true;
- }
- }
-
- return false;
- }
-
-
- bool InCollisionRight(){
- int newX, newY;
-
- for(int i = 0; i < 4; i++){
- newX = boardX + X[i];
- newY = boardY + Y[i] + 1;
-
- if(RightEdge(newY) || board[newX][newY] != 0){
- return true;
- }
- }
-
- return false;
- }
-// };
-
-// Tetromino currentTetromino;
-
-//////// PORTAPACK - USE BUTTONS VS JOYSTICK, AND ADDED PAUSE FEATURE:
-void ReadJoystickForFigure() {
- if(but_LEFT) {
- MoveLeft();
- }
- else if(but_RIGHT) {
- MoveRight();
- }
- else if(but_UP) {
- pause_game();
- }
- else if(but_DOWN) {
- SoftDrop();
- }
-}
-//////// PORTAPACK
-
-void CheckLines(short &firstLine, short &numberOfLines)
-{
- //vraća preko reference prvu liniju koja je popunjena do kraja, kao i takvih linija
- firstLine = -1; //postavljen na -1 jer ako nema linija koje treba brisati ispod u UpdateBoard neće se ući u petlju
- numberOfLines = 0;
- for(int i = 19; i >= 0; i--) {
- short temp = 0;
- for(int j = 0; j < 10; j++) {
- if(board[i][j] == 0) {
- if(numberOfLines > 0) return;
- break;
- }//ako je makar jedna bijela, prekida se brojanje
- temp++;
- }
- if(temp == 10) { //ako je temo došao do 10, niti jedna bijela - puna linija
- numberOfLines++;
- if(firstLine == -1) firstLine = i; //ovo mijenjamo samo prvi put
- }
- }
-}
-
-unsigned int UpdateScore (short numOfLines){
- unsigned int newIncrement = 0;
- switch(numOfLines) {
- case 1 : newIncrement = 40; break;
- case 2 : newIncrement = 100; break;
- case 3 : newIncrement = 300; break;
- case 4 : newIncrement = 1200; break;
- default : newIncrement = 0; break; //update funkcije za score, još sam vratio ovo na 0
- }
- return newIncrement * (level + 1);
-}
-
-void UpdateBoard()
-{
- short firstLine, numberOfLines;
- //pozivamo funkciju
- do {
- CheckLines(firstLine, numberOfLines);
- for(int i = firstLine; i >= numberOfLines; i--) {
- for(int j = 0; j < 10; j++) {
- board[i][j] = board[i - numberOfLines][j];
- board[i - numberOfLines][j] = 0;
- short tmp = i - numberOfLines;
- fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok
- fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White);
- if(board[i][j] != 0)
- rect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , Black);
- }
- }
- score += UpdateScore(numberOfLines);
- }
- while(numberOfLines != 0); //ovdje se mijenja globalna varijabla score
-}
-
-bool IsOver() {
- for(int i = 0; i < 10; i++) {
- if(board[0][i] != 0) return true;
- }
- return false;
-}
-
-void ShowGameOverScreen() {
-//////// PORTAPACK - SKIP CLS
-// cls();
-// background(Black);
-// foreground(White);
-background(White);
-foreground(Black);
-//////// PORTAPACK
- locate(60, 120);
- printf("GAME OVER");
- locate(40, 150);
- printf("YOUR SCORE IS %d", score);
- wait(5); //ovaj prikaz traje 3s (možemo mijenjati) a nakon toga se ponovo prikazuje meni sa levelima
-}
-
-void InitGame() {
- if(firstTime) {
- Tetromino(rand() % 7 + 1);
- DrawFigure();
- nextFigure = rand() % 7 + 1;
- ShowNextFigure();
- firstTime = false;
- }
-}
-
-void PlayGame(){
- InitGame();
- if(!MoveDown()){
- OnAttached();
- UpdateBoard();
- ShowScore();
-
- Tetromino(nextFigure);
- DrawFigure();
- nextFigure = rand() % 7 + 1;
- ShowNextFigure();
- if(IsOver()) {
- //ako je igra završena brišemo sve sa displey-a, prikazujemo poruku i score
- //takođe moramo dettach-at ticker
- game.detach();
- ShowGameOverScreen(); //prikaz da je kraj igre, ima wait od 3s
- EndPlay();
- }
- }
-
-}
-
-void OnTasterPressed(){
- if(debounceTaster.read_ms() > 200) {
- if(gameStarted){
- Rotate();
- }
- else{
- joystick.detach();
- gameStarted = true;
- StartGame(); //pocinje igra, prikazuje se tabla
- joystick.attach(&ReadJoystickForFigure, 0.3);
- game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
- }
- debounceTaster.reset();
- }
-}
-
-void SetTaster() {
- taster.mode(PullUp); //mora se aktivirati pull up otpornik na tasteru joystick-a
- taster.fall(&OnTasterPressed);
- debounceTaster.reset();
- debounceTaster.start();
-}
-
-int main() {
- srand(GenerateRandomSeed());
- Init();
- ShowLevelMenu();
- joystick.attach(&ReadJoystickForLevel, 0.3);
- SetTaster();
-
-//////// PORTAPACK: CAN'T HANG HERE IN MAYHEM OR WE WON'T GET ANY CALLBACKS
-// while(1);
-return 0;
-//////// PORTAPACK
-}
-
-//////// PORTAPACK - ADDED PAUSE FEATURE:
-void pause_game() {
- game.detach();
- joystick.detach();
- locate(180, 200);
- printf("PAUSED");
- while ((get_switches_state().to_ulong() & 0x10) == 0); // wait for SELECT button to resume
- printf(" ");
- joystick.attach(&ReadJoystickForFigure, 0.3);
- game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
-};
-//////// PORTAPACK
\ No newline at end of file
diff --git a/firmware/application/external/tetris/ui_tetris.cpp b/firmware/application/external/tetris/ui_tetris.cpp
index b236b2382..ce8da529e 100644
--- a/firmware/application/external/tetris/ui_tetris.cpp
+++ b/firmware/application/external/tetris/ui_tetris.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 Mark Thompson
+ * 2025 updates by RocketGod (https://betaskynet.com/)
*
* This file is part of PortaPack.
*
@@ -23,23 +24,576 @@
namespace ui::external_app::tetris {
-#pragma GCC diagnostic push
-// external code, so ignore warnings
-#pragma GCC diagnostic ignored "-Weffc++"
-#include "tetris.cpp"
-#pragma GCC diagnostic pop
+Ticker game;
+Ticker joystick;
+
+unsigned char level = 0;
+const float delays[4] = {1.2, 0.7, 0.4, 0.25};
+unsigned int score = 0;
+bool firstTime = true;
+bool gameStarted = false;
+unsigned char nextFigure = 1;
+short board[20][10] = {0};
+const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
+const short DIMENSION = 16;
+const short DIMENSION_NEXT = 12;
+short figuresX[7][4] = {{0, 0, 0, 0}, {0, 0, 1, 1}, {0, 1, 1, 1}, {1, 1, 0, 0}, {0, 1, 0, 1}, {1, 1, 1, 0}, {1, 1, 1, 0}};
+short figuresY[7][4] = {{0, 1, 2, 3}, {1, 0, 0, 1}, {1, 1, 2, 0}, {0, 1, 1, 2}, {0, 1, 1, 2}, {2, 1, 0, 0}, {0, 1, 2, 2}};
+
+const Color pp_colors[] = {
+ Color::white(),
+ Color::blue(),
+ Color::yellow(),
+ Color::purple(),
+ Color::green(),
+ Color::red(),
+ Color::magenta(),
+ Color::orange(),
+ Color::black(),
+};
+
+Painter painter;
+
+bool but_RIGHT = false;
+bool but_LEFT = false;
+bool but_UP = false;
+bool but_DOWN = false;
+bool but_SELECT = false;
+
+static int x_pos{0};
+static int y_pos{0};
+
+static int rotation_state = 0;
+
+static int fg_color;
+static int bg_color;
+
+static Callback fall_timer_callback = nullptr;
+static uint32_t fall_timer_timeout = 0;
+static uint32_t fall_timer_counter = 0;
+static Callback dir_button_callback = nullptr;
+
+void cls() {
+ painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
+}
+
+void background(int color) {
+ bg_color = color;
+}
+
+void foreground(int color) {
+ fg_color = color;
+}
+
+void locate(int x, int y) {
+ x_pos = x;
+ y_pos = y;
+}
+
+void fillrect(int x1, int y1, int x2, int y2, int color) {
+ painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void rect(int x1, int y1, int x2, int y2, int color) {
+ painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
+}
+
+void printf(std::string str) {
+ auto style = (fg_color == White) ? *ui::Theme::getInstance()->bg_darkest : *ui::Theme::getInstance()->fg_light;
+ painter.draw_string({x_pos, y_pos - 1}, style, str);
+}
+
+void printf(std::string str, int v) {
+ if (str.find_first_of("%") != std::string::npos) {
+ str.resize(str.find_first_of("%"));
+ }
+ printf(str + to_string_dec_uint(v));
+}
+
+void check_fall_timer() {
+ if (fall_timer_callback) {
+ if (++fall_timer_counter >= fall_timer_timeout) {
+ fall_timer_counter = 0;
+ fall_timer_callback();
+ }
+ }
+}
+
+void Ticker::attach(Callback func, double delay_sec) {
+ if (delay_sec == 0.3) {
+ dir_button_callback = func;
+ } else {
+ fall_timer_callback = func;
+ fall_timer_timeout = delay_sec * 60;
+ }
+}
+
+void Ticker::detach() {
+ dir_button_callback = nullptr;
+ fall_timer_callback = nullptr;
+}
+
+unsigned int GenerateRandomSeed() {
+ return LPC_RTC->CTIME0;
+}
+
+void Init() {
+}
+
+void ShowScore() {
+ fillrect(165, 10, 235, 60, Black);
+ rect(165, 10, 235, 60, White);
+ locate(200, 35);
+ printf("%d", score);
+}
+
+void ShowNextFigure() {
+ fillrect(165, 70, 235, 130, Black);
+ rect(165, 70, 235, 130, White);
+ int upperLeftX = 176, upperLeftY = 83;
+ for (int i = 0; i < 4; i++) {
+ int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
+ fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
+ rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
+ }
+}
+
+void DrawCursor(int color, unsigned char lev) {
+ fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
+}
+
+void ShowLevelMenu() {
+ cls();
+ background(Black);
+ foreground(White);
+ locate(80, 50);
+ printf("LEVEL 1");
+ locate(80, 120);
+ printf("LEVEL 2");
+ locate(80, 190);
+ printf("LEVEL 3");
+ locate(80, 260);
+ printf("LEVEL 4");
+ DrawCursor(White, level);
+}
+
+void ReadJoystickForLevel() {
+ unsigned char old = level;
+ if (but_UP) {
+ (level == 0) ? level = 3 : level--;
+ } else if (but_DOWN) {
+ level = (level + 1) % 4;
+ }
+ if (old != level) {
+ DrawCursor(Black, old);
+ DrawCursor(White, level);
+ }
+}
+
+void EndPlay() {
+ joystick.detach();
+ game.detach();
+ score = 0;
+ firstTime = true;
+ gameStarted = false;
+ for (int i = 0; i < 20; i++) {
+ for (int j = 0; j < 10; j++) {
+ board[i][j] = 0;
+ }
+ }
+ ShowLevelMenu();
+ joystick.attach(&ReadJoystickForLevel, 0.3);
+}
+
+void StartGame() {
+ cls();
+ background(Black);
+ foreground(White);
+ fillrect(0, 0, 162, screen_height, Black);
+ rect(162, 0, 164, screen_height, White);
+ fillrect(164, 0, screen_width, screen_height, Black);
+ ShowScore();
+ ShowNextFigure();
+}
+
+void copyCoordinates(short X[], short Y[], unsigned char index) {
+ for (int i = 0; i < 4; i++) {
+ X[i] = figuresX[index][i];
+ Y[i] = figuresY[index][i];
+ }
+}
+
+bool BottomEdge(int x) {
+ return x > 19;
+}
+
+bool LeftEdge(int y) {
+ return y < 0;
+}
+
+bool RightEdge(int y) {
+ return y > 9;
+}
+
+bool OutOfBounds(int y, int x) {
+ return y < 0 || y > 9 || x > 19;
+}
+
+void PutBorders(short x, short y) {
+ for (int i = x - 1; i <= x + 1; i++) {
+ for (int j = y - 1; j <= y + 1; j++) {
+ if (i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
+ rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
+ }
+ }
+}
+
+short X[4];
+short Y[4];
+short boardX, boardY;
+unsigned char colorIndex;
+
+void Tetromino(unsigned char c) {
+ Initialize(c);
+}
+
+void Initialize(unsigned char c) {
+ colorIndex = c;
+ boardY = 4;
+ if (c == 1) { // I-tetromino
+ boardX = -1; // Spawn higher
+ } else {
+ boardX = 0; // Other tetrominos spawn at top
+ }
+ copyCoordinates(X, Y, c - 1);
+ rotation_state = 0;
+}
+
+void Rotate() {
+ short newX[4], newY[4];
+ int next_state = (rotation_state + 1) % 4;
+
+ if (colorIndex == 2) { // O-tetromino doesn't rotate
+ return;
+ }
+
+ const short kick_tests_I[4][5][2] = {
+ {{0, 0}, {-2, 0}, {1, 0}, {-2, -1}, {1, 2}},
+ {{0, 0}, {-1, 0}, {2, 0}, {-1, 2}, {2, -1}},
+ {{0, 0}, {2, 0}, {-1, 0}, {2, 1}, {-1, -2}},
+ {{0, 0}, {1, 0}, {-2, 0}, {1, -2}, {-2, 1}}};
+ const short kick_tests_other[4][5][2] = {
+ {{0, 0}, {-1, 0}, {-1, 1}, {0, -2}, {-1, -2}},
+ {{0, 0}, {1, 0}, {1, -1}, {0, 2}, {1, 2}},
+ {{0, 0}, {1, 0}, {1, 1}, {0, -2}, {1, -2}},
+ {{0, 0}, {-1, 0}, {-1, -1}, {0, 2}, {-1, 2}}};
+
+ bool is_I_tetromino = (colorIndex == 1);
+ const short(*kick_tests)[5][2] = is_I_tetromino ? kick_tests_I : kick_tests_other;
+
+ for (int test = 0; test < 5; test++) {
+ short kickX = kick_tests[rotation_state][test][0];
+ short kickY = kick_tests[rotation_state][test][1];
+
+ // Calculate new positions
+ for (int i = 0; i < 4; i++) {
+ short tmpX = X[i] - X[1];
+ short tmpY = Y[i] - Y[1];
+ newX[i] = X[1] - tmpY;
+ newY[i] = Y[1] + tmpX;
+ int testX = boardX + newX[i] + kickX;
+ int testY = boardY + newY[i] + kickY;
+ // Explicitly block rotations that place blocks above the board
+ if (testX < 0 || OutOfBounds(testY, testX) || (testX >= 0 && board[testX][testY] != 0)) {
+ goto next_test;
+ }
+ }
+
+ // Valid rotation found, apply it
+ DeleteFigure();
+ for (int i = 0; i < 4; i++) {
+ X[i] = newX[i];
+ Y[i] = newY[i];
+ }
+ boardX += kickX;
+ boardY += kickY;
+ rotation_state = next_state;
+ DrawFigure();
+ return;
+
+ next_test:
+ continue;
+ }
+}
+
+void DrawFigure() {
+ for (int i = 0; i < 4; i++) {
+ int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
+ fillrect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
+ rect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
+ }
+}
+
+void DeleteFigure() {
+ for (int i = 0; i < 4; i++) {
+ short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
+ fillrect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
+ PutBorders(upperLeftY, upperLeftX);
+ }
+}
+
+void OnAttached() {
+ for (int i = 0; i < 4; i++) {
+ board[boardX + X[i]][boardY + Y[i]] = colorIndex;
+ }
+}
+
+bool MoveDown(char delta) {
+ if (!InCollisionDown(delta)) {
+ DeleteFigure();
+ boardX += delta;
+ DrawFigure();
+ return true;
+ }
+ return false;
+}
+
+void MoveLeft() {
+ if (!InCollisionLeft()) {
+ DeleteFigure();
+ boardY--;
+ DrawFigure();
+ }
+}
+
+void MoveRight() {
+ if (!InCollisionRight()) {
+ DeleteFigure();
+ boardY++;
+ DrawFigure();
+ }
+}
+
+void SoftDrop() {
+ DeleteFigure();
+ MoveDown(2);
+ DrawFigure();
+ score += 2 * (level + 1);
+ ShowScore();
+}
+
+bool InCollisionDown(char delta) {
+ int newX, newY;
+ for (int i = 0; i < 4; i++) {
+ newX = boardX + X[i] + delta;
+ newY = boardY + Y[i];
+ if (BottomEdge(newX) || board[newX][newY] != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InCollisionLeft() {
+ int newX, newY;
+ for (int i = 0; i < 4; i++) {
+ newX = boardX + X[i];
+ newY = boardY + Y[i] - 1;
+ if (LeftEdge(newY) || board[newX][newY] != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InCollisionRight() {
+ int newX, newY;
+ for (int i = 0; i < 4; i++) {
+ newX = boardX + X[i];
+ newY = boardY + Y[i] + 1;
+ if (RightEdge(newY) || board[newX][newY] != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ReadJoystickForFigure() {
+ if (but_LEFT) {
+ MoveLeft();
+ } else if (but_RIGHT) {
+ MoveRight();
+ } else if (but_UP) {
+ pause_game();
+ } else if (but_DOWN) {
+ SoftDrop();
+ } else if (but_SELECT) {
+ Rotate();
+ }
+}
+
+void CheckLines(short& firstLine, short& numberOfLines) {
+ firstLine = -1;
+ numberOfLines = 0;
+ for (int i = 19; i >= 0; i--) {
+ short temp = 0;
+ for (int j = 0; j < 10; j++) {
+ if (board[i][j] == 0) {
+ if (numberOfLines > 0) return;
+ break;
+ }
+ temp++;
+ }
+ if (temp == 10) {
+ numberOfLines++;
+ if (firstLine == -1) firstLine = i;
+ }
+ }
+}
+
+unsigned int UpdateScore(short numOfLines) {
+ unsigned int newIncrement = 0;
+ switch (numOfLines) {
+ case 1:
+ newIncrement = 40;
+ break;
+ case 2:
+ newIncrement = 100;
+ break;
+ case 3:
+ newIncrement = 300;
+ break;
+ case 4:
+ newIncrement = 1200;
+ break;
+ default:
+ newIncrement = 0;
+ break;
+ }
+ return newIncrement * (level + 1);
+}
+
+void UpdateBoard() {
+ short firstLine, numberOfLines;
+ do {
+ CheckLines(firstLine, numberOfLines);
+ for (int i = firstLine; i >= numberOfLines; i--) {
+ for (int j = 0; j < 10; j++) {
+ board[i][j] = board[i - numberOfLines][j];
+ board[i - numberOfLines][j] = 0;
+ }
+ }
+ fillrect(0, 0, 162, screen_height, Black);
+ for (int i = 0; i < 20; i++) {
+ for (int j = 0; j < 10; j++) {
+ if (board[i][j] != 0) {
+ fillrect(j * DIMENSION, i * DIMENSION, (j + 1) * DIMENSION, (i + 1) * DIMENSION, colors[board[i][j]]);
+ rect(j * DIMENSION, i * DIMENSION, (j + 1) * DIMENSION, (i + 1) * DIMENSION, Black);
+ }
+ }
+ }
+ score += UpdateScore(numberOfLines);
+ DrawFigure();
+ } while (numberOfLines != 0);
+}
+
+bool IsOver() {
+ for (int i = 0; i < 10; i++) {
+ if (board[0][i] != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ShowGameOverScreen() {
+ background(Black);
+ foreground(White);
+ locate(60, 120);
+ printf("GAME OVER");
+ locate(40, 150);
+ printf("YOUR SCORE IS %d", score);
+ wait(5);
+}
+
+void InitGame() {
+ if (firstTime) {
+ Tetromino(rand() % 7 + 1);
+ DrawFigure();
+ nextFigure = rand() % 7 + 1;
+ ShowNextFigure();
+ firstTime = false;
+ }
+}
+
+void PlayGame() {
+ InitGame();
+ if (!MoveDown(1)) {
+ OnAttached();
+ UpdateBoard();
+ ShowScore();
+ Tetromino(nextFigure);
+ DrawFigure();
+ nextFigure = rand() % 7 + 1;
+ ShowNextFigure();
+ if (IsOver()) {
+ game.detach();
+ ShowGameOverScreen();
+ EndPlay();
+ }
+ }
+}
+
+void OnTasterPressed() {
+ static uint32_t debounce_counter = 0;
+ const uint32_t debounce_threshold = 12;
+
+ if (debounce_counter == 0) {
+ if (!gameStarted) {
+ joystick.detach();
+ gameStarted = true;
+ StartGame();
+ joystick.attach(&ReadJoystickForFigure, 0.3);
+ game.attach(&PlayGame, delays[level]);
+ }
+ debounce_counter = debounce_threshold;
+ } else if (debounce_counter > 0) {
+ debounce_counter--;
+ }
+}
+
+void pause_game() {
+ game.detach();
+ joystick.detach();
+ locate(180, 200);
+ printf("PAUSED");
+ while ((get_switches_state().to_ulong() & 0x10) == 0)
+ ;
+ printf(" ");
+ joystick.attach(&ReadJoystickForFigure, 0.3);
+ game.attach(&PlayGame, delays[level]);
+}
+
+int main() {
+ std::srand(GenerateRandomSeed());
+ Init();
+ ShowLevelMenu();
+ joystick.attach(&ReadJoystickForLevel, 0.3);
+ return 0;
+}
TetrisView::TetrisView(NavigationView& nav)
- : nav_(nav) {
+ : nav_{nav} {
add_children({&dummy});
}
+void TetrisView::on_show() {
+}
+
void TetrisView::paint(Painter& painter) {
(void)painter;
-
if (!initialized) {
initialized = true;
- std::srand(LPC_RTC->CTIME0);
main();
}
}
@@ -50,11 +604,45 @@ void TetrisView::frame_sync() {
}
bool TetrisView::on_encoder(const EncoderEvent delta) {
- return check_encoder(delta);
+ if (!gameStarted) {
+ unsigned char old = level;
+ if (delta > 0) {
+ level = (level + 1) % 4;
+ } else if (delta < 0) {
+ (level == 0) ? level = 3 : level--;
+ }
+ if (old != level) {
+ DrawCursor(Black, old);
+ DrawCursor(White, level);
+ }
+ } else if (gameStarted && delta != 0) {
+ Rotate();
+ }
+ set_dirty();
+ return true;
}
bool TetrisView::on_key(const KeyEvent key) {
- return check_key(key);
+ auto switches_debounced = get_switches_state().to_ulong();
+ but_RIGHT = (switches_debounced & 0x01) != 0;
+ but_LEFT = (switches_debounced & 0x02) != 0;
+ but_DOWN = (switches_debounced & 0x04) != 0;
+ but_UP = (switches_debounced & 0x08) != 0;
+ but_SELECT = (switches_debounced & 0x10) != 0;
+
+ if (key == KeyEvent::Select) {
+ if (!gameStarted) {
+ OnTasterPressed();
+ } else {
+ Rotate();
+ }
+ } else if (gameStarted) {
+ ReadJoystickForFigure();
+ } else {
+ ReadJoystickForLevel();
+ }
+ set_dirty();
+ return true;
}
-} // namespace ui::external_app::tetris
+} // namespace ui::external_app::tetris
\ No newline at end of file
diff --git a/firmware/application/external/tetris/ui_tetris.hpp b/firmware/application/external/tetris/ui_tetris.hpp
index 44bb77885..f1f8738b0 100644
--- a/firmware/application/external/tetris/ui_tetris.hpp
+++ b/firmware/application/external/tetris/ui_tetris.hpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 Mark Thompson
+ * 2025 updates by RocketGod (https://betaskynet.com/)
*
* This file is part of PortaPack.
*
@@ -22,6 +23,7 @@
#ifndef __UI_TETRIS_H__
#define __UI_TETRIS_H__
+#include "ui.hpp"
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
@@ -29,16 +31,113 @@
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "limits.h"
+#include "ui_widget.hpp"
namespace ui::external_app::tetris {
+enum {
+ White,
+ Blue,
+ Yellow,
+ Purple,
+ Green,
+ Red,
+ Maroon,
+ Orange,
+ Black,
+};
+
+extern const Color pp_colors[];
+extern Painter painter;
+extern bool but_RIGHT;
+extern bool but_LEFT;
+extern bool but_UP;
+extern bool but_DOWN;
+extern bool but_SELECT;
+
+void cls();
+void background(int color);
+void foreground(int color);
+void locate(int x, int y);
+void fillrect(int x1, int y1, int x2, int y2, int color);
+void rect(int x1, int y1, int x2, int y2, int color);
+void printf(std::string str);
+void printf(std::string str, int v);
+
+#define wait(x) chThdSleepMilliseconds(x * 1000)
+
+using Callback = void (*)(void);
+
+class Ticker {
+ public:
+ Ticker() = default;
+ void attach(Callback func, double delay_sec);
+ void detach();
+};
+
+extern Ticker game;
+extern Ticker joystick;
+
+extern unsigned char level;
+extern const float delays[4];
+extern unsigned int score;
+extern bool firstTime;
+extern bool gameStarted;
+extern unsigned char nextFigure;
+extern short board[20][10];
+extern const int colors[8];
+extern const short DIMENSION;
+extern const short DIMENSION_NEXT;
+extern short figuresX[7][4];
+extern short figuresY[7][4];
+
+unsigned int GenerateRandomSeed();
+void Init();
+void ShowScore();
+void ShowNextFigure();
+void DrawCursor(int color, unsigned char lev);
+void ShowLevelMenu();
+void ReadJoystickForLevel();
+void EndPlay();
+void StartGame();
+void copyCoordinates(short X[], short Y[], unsigned char index);
+bool BottomEdge(int x);
+bool LeftEdge(int y);
+bool RightEdge(int y);
+bool OutOfBounds(int y, int x);
+void PutBorders(short x, short y);
+void Tetromino(unsigned char c);
+void Initialize(unsigned char c);
+void Rotate();
+void DrawFigure();
+void DeleteFigure();
+void OnAttached();
+bool MoveDown(char delta);
+void MoveLeft();
+void MoveRight();
+void SoftDrop();
+bool InCollisionDown(char delta);
+bool InCollisionLeft();
+bool InCollisionRight();
+void ReadJoystickForFigure();
+void CheckLines(short& firstLine, short& numberOfLines);
+unsigned int UpdateScore(short numOfLines);
+void UpdateBoard();
+bool IsOver();
+void ShowGameOverScreen();
+void InitGame();
+void PlayGame();
+void OnTasterPressed();
+void pause_game();
+
class TetrisView : public View {
public:
TetrisView(NavigationView& nav);
+ void on_show() override;
- std::string title() const override { return "Tetris"; };
+ std::string title() const override { return "Tetris"; }
- void focus() override { dummy.focus(); };
+ void focus() override { dummy.focus(); }
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
@@ -49,7 +148,7 @@ class TetrisView : public View {
NavigationView& nav_;
Button dummy{
- {240, 0, 0, 0},
+ {screen_width, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
@@ -61,4 +160,4 @@ class TetrisView : public View {
} // namespace ui::external_app::tetris
-#endif /*__UI_TETRIS_H__*/
+#endif /* __UI_TETRIS_H__ */
\ No newline at end of file
diff --git a/firmware/application/external/tpmsrx/tpms_app.hpp b/firmware/application/external/tpmsrx/tpms_app.hpp
index 8746711fe..05766441b 100644
--- a/firmware/application/external/tpmsrx/tpms_app.hpp
+++ b/firmware/application/external/tpmsrx/tpms_app.hpp
@@ -138,7 +138,7 @@ class TPMSAppView : public View {
};
AudioVolumeField field_volume{
- {28 * 8, 0 * 16}};
+ {screen_width - 2 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
diff --git a/firmware/application/external/tuner/ui_tuner.hpp b/firmware/application/external/tuner/ui_tuner.hpp
index 7b76d0404..ad5f03949 100644
--- a/firmware/application/external/tuner/ui_tuner.hpp
+++ b/firmware/application/external/tuner/ui_tuner.hpp
@@ -76,12 +76,12 @@ class TunerView : public View {
{{0 * 8, 5 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
Text text_note_frequency{
- {(sizeof("Note Frequency:") + 1) * 8, 3 * 16, screen_width - (sizeof("Note Frequency:") + 1) * 8, 16},
+ {(sizeof("Note Frequency:") + 1) * 8, 3 * 16, (int)screen_width - (int)(sizeof("Note Frequency:") + 1) * 8, 16},
"",
};
Text text_note_octave_shift{
- {(sizeof("Note Octave Shift:") + 1) * 8, 4 * 16, screen_width - (sizeof("Note Octave Shift:") + 1) * 8, 16},
+ {(sizeof("Note Octave Shift:") + 1) * 8, 4 * 16, (int)screen_width - (int)(sizeof("Note Octave Shift:") + 1) * 8, 16},
"",
};
diff --git a/firmware/application/external/wardrivemap/ui_wardrivemap.hpp b/firmware/application/external/wardrivemap/ui_wardrivemap.hpp
index 759cd0110..6d7608583 100644
--- a/firmware/application/external/wardrivemap/ui_wardrivemap.hpp
+++ b/firmware/application/external/wardrivemap/ui_wardrivemap.hpp
@@ -49,12 +49,12 @@ class WardriveMapView : public View {
NavigationView& nav_;
Text text_info{{0 * 8, 0 * 8, 20 * 8, 16 * 1}, "0 / 30"};
- Text text_notfound{{0 * 8, 0 * 8, 30 * 8, 16 * 1}, "No GeoTagged captures found"};
+ Text text_notfound{{0 * 8, 0 * 8, screen_width, 16 * 1}, "No GeoTagged captures found"};
GeoPos geopos{
{0, 20},
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
- GeoMap geomap{{0, 75, 240, 320 - 75}};
+ GeoMap geomap{{0, 75, screen_width, screen_height - 75}};
Button btn_back{{22 * 8, 0 * 8, 3 * 8, 16}, "<-"};
Button btn_next{{26 * 8, 0 * 8, 3 * 8, 16}, "->"};
diff --git a/firmware/application/external/wav_view/main.cpp b/firmware/application/external/wav_view/main.cpp
new file mode 100644
index 000000000..1d7d72fdc
--- /dev/null
+++ b/firmware/application/external/wav_view/main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "ui_view_wav.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::view_wav {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::view_wav
+
+extern "C" {
+
+__attribute__((section(".external_app.app_view_wav.application_information"), used)) application_information_t _application_information_view_wav = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::view_wav::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "WAV Viewer",
+ /*.bitmap_data = */ {0xF0, 0x0F, 0x1C, 0x18, 0x17, 0x38, 0x15, 0x78, 0x15, 0xF8, 0x15, 0x82, 0x15, 0x8B, 0xD5, 0x83, 0xD5, 0xBB, 0xD5, 0x83, 0x15, 0x8B, 0x15, 0x92, 0x15, 0xA0, 0x17, 0x80, 0x1C, 0x80, 0xF0, 0xFF},
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::UTILITIES,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'T', 'X'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/apps/ui_view_wav.cpp b/firmware/application/external/wav_view/ui_view_wav.cpp
similarity index 99%
rename from firmware/application/apps/ui_view_wav.cpp
rename to firmware/application/external/wav_view/ui_view_wav.cpp
index 1272ca8ed..1594c1d96 100644
--- a/firmware/application/apps/ui_view_wav.cpp
+++ b/firmware/application/external/wav_view/ui_view_wav.cpp
@@ -28,8 +28,9 @@
#include "string_format.hpp"
using namespace portapack;
+using namespace ui;
-namespace ui {
+namespace ui::external_app::view_wav {
void ViewWavView::update_scale(int32_t new_scale) {
scale = new_scale;
@@ -345,4 +346,4 @@ ViewWavView::~ViewWavView() {
baseband::shutdown();
}
-} /* namespace ui */
+} /* namespace ui::external_app::view_wav */
diff --git a/firmware/application/apps/ui_view_wav.hpp b/firmware/application/external/wav_view/ui_view_wav.hpp
similarity index 95%
rename from firmware/application/apps/ui_view_wav.hpp
rename to firmware/application/external/wav_view/ui_view_wav.hpp
index b460855a8..a358c30db 100644
--- a/firmware/application/apps/ui_view_wav.hpp
+++ b/firmware/application/external/wav_view/ui_view_wav.hpp
@@ -28,7 +28,9 @@
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
-namespace ui {
+using namespace ui;
+
+namespace ui::external_app::view_wav {
class ViewWavView : public View {
public:
@@ -116,10 +118,10 @@ class ViewWavView : public View {
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
AudioVolumeField field_volume{
- {28 * 8, 18 * 16}};
+ {screen_width - 2 * 8, 18 * 16}};
Waveform waveform{
- {0, 5 * 16, 240, 64},
+ {0, 5 * 16, screen_width, 64},
waveform_buffer,
240,
0,
@@ -127,7 +129,7 @@ class ViewWavView : public View {
Theme::getInstance()->bg_darkest->foreground};
ProgressBar progressbar{
- {0 * 8, 11 * 16, 30 * 8, 4}};
+ {0 * 8, 11 * 16, screen_width, 4}};
NumberField field_pos_seconds{
{9 * 8, 12 * 16},
@@ -175,7 +177,7 @@ class ViewWavView : public View {
true};
Text text_delta{
- {7 * 8, 16 * 16, 30 * 8, 16},
+ {7 * 8, 16 * 16, screen_width, 16},
"-"};
MessageHandlerRegistration message_handler_replay_thread_error{
@@ -202,4 +204,4 @@ class ViewWavView : public View {
}};
};
-} /* namespace ui */
+} /* namespace ui::external_app::view_wav */
diff --git a/firmware/application/external/wefax_rx/main.cpp b/firmware/application/external/wefax_rx/main.cpp
new file mode 100644
index 000000000..0ab7f5e7e
--- /dev/null
+++ b/firmware/application/external/wefax_rx/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_wefax_rx.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::wefax_rx {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::wefax_rx
+
+extern "C" {
+
+__attribute__((section(".external_app.app_wefax_rx.application_information"), used)) application_information_t _application_information_wefax_rx = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::wefax_rx::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "WeFax",
+ /*.bitmap_data = */ {
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0x07,
+ 0x04,
+ 0x0C,
+ 0x04,
+ 0x1C,
+ 0x04,
+ 0x3C,
+ 0x84,
+ 0x21,
+ 0x84,
+ 0x21,
+ 0x84,
+ 0x21,
+ 0xF4,
+ 0x2F,
+ 0xF4,
+ 0x2F,
+ 0x84,
+ 0x21,
+ 0x84,
+ 0x21,
+ 0x84,
+ 0x21,
+ 0x04,
+ 0x20,
+ 0xFC,
+ 0x3F,
+ 0x00,
+ 0x00,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_wefaxrx */ {'P', 'W', 'F', 'X'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/wefax_rx/ui_wefax_rx.cpp b/firmware/application/external/wefax_rx/ui_wefax_rx.cpp
new file mode 100644
index 000000000..344be5849
--- /dev/null
+++ b/firmware/application/external/wefax_rx/ui_wefax_rx.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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.
+ */
+
+/*
+TODOS LATER:
+ - add load data from wav file (maybe to a separate app, not this)
+ - AGC?!?
+ - fix and enable sync detection
+ - auto start / stop bmp save on each image
+*/
+
+#include "ui_wefax_rx.hpp"
+
+#include "audio.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+#include "lcd_ili9341.hpp"
+
+using namespace portapack;
+using namespace modems;
+using namespace ui;
+
+namespace ui::external_app::wefax_rx {
+
+void WeFaxRxView::focus() {
+ field_frequency.focus();
+}
+
+WeFaxRxView::WeFaxRxView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+ add_children({&rssi,
+ &field_rf_amp,
+ &field_lna,
+ &field_vga,
+ &field_volume,
+ &field_frequency,
+ &txt_status,
+ &labels,
+ &options_lpm,
+ &options_ioc,
+ &button_ss});
+
+ options_lpm.on_change = [this](size_t index, int32_t v) {
+ lpm_index = (uint8_t)index;
+ (void)v;
+ on_settings_changed();
+ };
+ options_ioc.on_change = [this](size_t index, int32_t v) {
+ ioc_index = (uint8_t)index;
+ (void)v;
+ on_settings_changed();
+ };
+
+ field_frequency.set_step(100);
+ field_frequency.on_edit_shown = [this]() {
+ paused = true;
+ };
+ field_frequency.on_edit_hidden = [this]() {
+ paused = false;
+ };
+ audio::output::start();
+ receiver_model.set_hidden_offset(WEFAX_FREQ_OFFSET);
+ receiver_model.set_sampling_rate(3072000); // set the needed baseband SR.
+ receiver_model.set_baseband_bandwidth(1750000); // set the front-end RF BW filter.
+ receiver_model.enable();
+
+ txt_status.set("Waiting for signal.");
+
+ button_ss.on_select = [this](Button&) {
+ if (bmp.is_loaded()) {
+ bmp.close();
+ button_ss.set_text(LanguageHelper::currentMessages[LANG_START]);
+ return;
+ }
+ ensure_directory("/BMP");
+ bmp.create("/BMP/wefax_" + to_string_timestamp(rtc_time::now()) + ".bmp", WEFAX_PX_SIZE, 1);
+ button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]);
+ };
+
+ options_lpm.set_selected_index(lpm_index, false);
+ options_ioc.set_selected_index(ioc_index, true);
+}
+
+WeFaxRxView::~WeFaxRxView() {
+ stopping = true;
+ receiver_model.set_hidden_offset(0);
+ bmp.close();
+ receiver_model.disable();
+ baseband::shutdown();
+ audio::output::stop();
+}
+
+void WeFaxRxView::on_settings_changed() {
+ baseband::set_wefax_config(options_lpm.selected_index_value(), options_ioc.selected_index_value());
+}
+
+void WeFaxRxView::on_status(WeFaxRxStatusDataMessage msg) {
+ (void)msg;
+ std::string tmp = "";
+ if (msg.state == 0) {
+ tmp = "Waiting for signal.";
+ } else if (msg.state == 1) {
+ tmp = "Synced.";
+ } else if (msg.state == 2) {
+ tmp = "Image arriving.";
+ }
+ txt_status.set(tmp);
+}
+
+// this stores and displays the image. keep it as simple as you can. a bit more complexity will kill the sync
+void WeFaxRxView::on_image(WeFaxRxImageDataMessage msg) {
+ if ((line_num) >= screen_height - 4 * 16) line_num = 0; // for draw reset
+
+ for (uint16_t i = 0; i < msg.cnt; i += 1) {
+ Color pxl = {msg.image[i], msg.image[i], msg.image[i]};
+ bmp.write_next_px(pxl);
+ line_in_part++;
+ if (line_in_part == WEFAX_PX_SIZE) {
+ line_in_part = 0;
+ line_num++;
+ bmp.expand_y_delta(1);
+ }
+
+ uint16_t xpos = line_in_part / (WEFAX_PX_SIZE / 240);
+ if (xpos >= 240) xpos = 239;
+ line_buffer[xpos] = pxl;
+ if ((line_in_part == 0)) {
+ portapack::display.render_line({0, line_num + 4 * 16}, 240, line_buffer);
+ }
+ }
+}
+
+} // namespace ui::external_app::wefax_rx
diff --git a/firmware/application/external/wefax_rx/ui_wefax_rx.hpp b/firmware/application/external/wefax_rx/ui_wefax_rx.hpp
new file mode 100644
index 000000000..36da65e2a
--- /dev/null
+++ b/firmware/application/external/wefax_rx/ui_wefax_rx.hpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_WEFAX_RX_H__
+#define __UI_WEFAX_RX_H__
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "ui_geomap.hpp"
+#include "ui_freq_field.hpp"
+#include "ui_record_view.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "log_file.hpp"
+#include "utility.hpp"
+#include "ui_fileman.hpp"
+#include "bmpfile.hpp"
+#include "file_path.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::wefax_rx {
+
+#define WEFAX_PX_SIZE 840
+// 1.900Hz is the usual center FM freq. , but as I added 300-Hz margin, + the truncated filter extended effect , in our case, we got better S/N if we use -2200 offset area. @Brumi
+#define WEFAX_FREQ_OFFSET -2200
+
+class WeFaxRxView : public View {
+ public:
+ WeFaxRxView(NavigationView& nav);
+ ~WeFaxRxView();
+
+ void focus() override;
+
+ std::string title() const override { return "WeFax"; };
+
+ private:
+ void on_settings_changed();
+ void on_status(WeFaxRxStatusDataMessage msg);
+ void on_image(WeFaxRxImageDataMessage msg);
+
+ bool stopping = false;
+
+ uint8_t ioc_index{0};
+ uint8_t lpm_index{0};
+ uint16_t line_num = 0; // nth line
+ uint16_t line_in_part = 0; // got multiple parts of a line, so keep track of it
+ uint8_t delayer = 0;
+ ui::Color line_buffer[240];
+ std::filesystem::path filetohandle = "";
+
+ bool paused = false; // when freq field is shown for example, we need to pause
+
+ BMPFile bmp{};
+
+ NavigationView& nav_;
+ RxRadioState radio_state_{};
+ app_settings::SettingsManager settings_{
+ "rx_wefax",
+ app_settings::Mode::RX,
+ {
+ {"ioc_index"sv, &ioc_index},
+ {"lpm_index"sv, &lpm_index},
+ }};
+
+ RFAmpField field_rf_amp{
+ {13 * 8, 0 * 16}};
+ LNAGainField field_lna{
+ {15 * 8, 0 * 16}};
+ VGAGainField field_vga{
+ {18 * 8, 0 * 16}};
+ RSSI rssi{
+ {21 * 8, 0, 6 * 8, 4}};
+ AudioVolumeField field_volume{
+ {screen_width - 2 * 8, 0 * 16}};
+
+ RxFrequencyField field_frequency{
+ {0 * 8, 0 * 16},
+ nav_};
+
+ Labels labels{
+ {{1 * 8, 1 * 16}, "LPM:", Theme::getInstance()->fg_light->foreground},
+ {{13 * 8, 1 * 16}, "IOC:", Theme::getInstance()->fg_light->foreground},
+ };
+
+ OptionsField options_lpm{
+ {6 * 8, 1 * 16},
+ 4,
+ {
+ {"60", 60},
+ {"90", 90},
+ {"100", 100},
+ {"120", 120},
+ {"180", 180},
+ {"240", 240},
+ }};
+
+ OptionsField options_ioc{
+ {18 * 8, 1 * 16},
+ 4,
+ {
+ {"576", 0},
+ {"228", 1},
+ }};
+
+ Text txt_status{
+ {0 * 8, 2 * 16, 20 * 8, 16},
+ };
+
+ Button button_ss{
+ {190, 2 * 16, 5 * 8, 16},
+ LanguageHelper::currentMessages[LANG_START]};
+
+ MessageHandlerRegistration message_handler_stats{
+ Message::ID::WeFaxRxStatusData,
+ [this](const Message* const p) {
+ if (stopping || paused) return;
+ const auto message = *reinterpret_cast(p);
+ on_status(message);
+ }};
+
+ MessageHandlerRegistration message_handler_image{
+ Message::ID::WeFaxRxImageData,
+ [this](const Message* const p) {
+ if (stopping || paused) return;
+ const auto message = *reinterpret_cast(p);
+ on_image(message);
+ }};
+};
+
+} // namespace ui::external_app::wefax_rx
+
+#endif /*__UI_WEFAX_RX_H__*/
diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp
index dae33f01c..6da93056e 100644
--- a/firmware/application/file.cpp
+++ b/firmware/application/file.cpp
@@ -50,6 +50,10 @@ Optional File::open_fatfs(const std::filesystem::path& filename, BY
}
}
+/*
+ * @param read_only: open in readonly mode
+ * @param create: create if it doesnt exist
+ */
Optional File::open(const std::filesystem::path& filename, bool read_only, bool create) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
if (create)
diff --git a/firmware/application/file_path.cpp b/firmware/application/file_path.cpp
index ce4acf48c..a08811fb6 100644
--- a/firmware/application/file_path.cpp
+++ b/firmware/application/file_path.cpp
@@ -49,3 +49,7 @@ const std::filesystem::path sstv_dir = u"SSTV";
const std::filesystem::path wav_dir = u"WAV";
const std::filesystem::path whipcalc_dir = u"WHIPCALC";
const std::filesystem::path ook_editor_dir = u"OOKFILES";
+const std::filesystem::path hopper_dir = u"HOPPER";
+const std::filesystem::path subghz_dir = u"SUBGHZ";
+const std::filesystem::path waterfalls_dir = u"WATERFALLS";
+const std::filesystem::path macaddress_dir = u"MACADDRESS";
diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp
index bc115736c..0f8548ee0 100644
--- a/firmware/application/file_path.hpp
+++ b/firmware/application/file_path.hpp
@@ -51,5 +51,9 @@ extern const std::filesystem::path sstv_dir;
extern const std::filesystem::path wav_dir;
extern const std::filesystem::path whipcalc_dir;
extern const std::filesystem::path ook_editor_dir;
+extern const std::filesystem::path hopper_dir;
+extern const std::filesystem::path subghz_dir;
+extern const std::filesystem::path waterfalls_dir;
+extern const std::filesystem::path macaddress_dir;
#endif /* __FILE_PATH_H__ */
diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp
index 74bf9198e..ab39a08e0 100644
--- a/firmware/application/freqman.hpp
+++ b/firmware/application/freqman.hpp
@@ -41,7 +41,9 @@ enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
- SPEC_MODULATION
+ SPEC_MODULATION,
+ AMFM_MODULATION, // Added for HF Wefax.demod APT signal
+ WFMAM_MODULATION // Added for NOAA 137 Mhz satellite band, demod APT signal.
};
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
diff --git a/firmware/application/freqman_db.cpp b/firmware/application/freqman_db.cpp
index 89eb52b14..659e759d5 100644
--- a/firmware/application/freqman_db.cpp
+++ b/firmware/application/freqman_db.cpp
@@ -49,9 +49,11 @@ options_t freqman_modulations = {
{"NFM", 1},
{"WFM", 2},
{"SPEC", 3},
+ {"AMFM", 4}, // To handle HF Wefax AM and FM demod. inside Audio App.
+ {"FMAM", 5}, // To handle NOAA 137 Mhz Sat FM and AM demod inside Audio App.
};
-options_t freqman_bandwidths[4] = {
+options_t freqman_bandwidths[6] = {
{
// AM
{"DSB 9k", 0},
@@ -68,7 +70,7 @@ options_t freqman_bandwidths[4] = {
},
{
// WFM
- {"40k", 2},
+ {"80k", 2},
{"180k", 1},
{"200k", 0},
},
@@ -100,10 +102,22 @@ options_t freqman_bandwidths[4] = {
{"5000k", 5500000},
{"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC).
},
+ {
+ // AMFM for Wefax-
+ {"USB+FM(Wefax Apt)", 5}, // Fixed RX demod. AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones.
+ },
+ {
+ // WFMAM for NOAA satellites, 137 Mhz band
+ {"80k-NOAA Apt LPF", 0}, // Captured RF IQ filtered BW 80K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
+ {"38k-NOAA Apt LPF", 1}, // Captured RF IQ filtered BW 38K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
+ {"38k-NOAA Apt BPF", 2}, // Captured RF IQ filtered BW 38K, APT baseband filtered BPF centred to the 2k4 AM subcarrier, BW = 2KHz
+ },
};
// TODO: these should be indexes.
options_t freqman_steps = {
+ {"10Hz ", 10},
+ {"50Hz ", 50},
{"0.1kHz ", 100},
{"1kHz ", 1000},
{"5kHz (SA AM)", 5000},
@@ -124,6 +138,8 @@ options_t freqman_steps = {
// TODO: these should be indexes.
options_t freqman_steps_short = {
+ {"10Hz", 10},
+ {"50Hz", 50},
{"0.1kHz", 100},
{"1kHz", 1000},
{"5kHz", 5000},
diff --git a/firmware/application/gradient.cpp b/firmware/application/gradient.cpp
new file mode 100644
index 000000000..2ca27e6bb
--- /dev/null
+++ b/firmware/application/gradient.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 Belousov Oleg
+ *
+ * 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 "gradient.hpp"
+
+#include "convert.hpp"
+#include "file_reader.hpp"
+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;
+}
+
+void Gradient::set_default() {
+ step(86, 0, 0, 255);
+ step(171, 0, 255, 0);
+ step(255, 255, 0, 0);
+}
+
+bool Gradient::load_file(const std::filesystem::path& file_path) {
+ File gradient_file;
+ auto error = gradient_file.open(file_path.string());
+
+ if (error)
+ return false;
+
+ auto reader = FileLineReader(gradient_file);
+ for (const auto& line : reader) {
+ if (line.length() == 0 || line[0] == '#')
+ continue; // Empty or comment line.
+
+ auto cols = split_string(line, ',');
+
+ if (cols.size() == 4) {
+ int16_t index, r, g, b;
+
+ if (!parse_int(cols[0], index) || index < 0 || index > 255)
+ continue;
+
+ if (!parse_int(cols[1], r) || r < 0 || r > 255)
+ continue;
+
+ if (!parse_int(cols[2], g) || g < 0 || g > 255)
+ continue;
+
+ if (!parse_int(cols[3], b) || b < 0 || b > 255)
+ continue;
+
+ step(index, r, g, 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;
+
+ 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;
+
+ lut[i] = ui::Color(new_r, new_g, new_b);
+ }
+
+ prev_index = index;
+ prev_r = r;
+ prev_g = g;
+ prev_b = b;
+}
diff --git a/firmware/application/external/tetris/Arial12x12.h b/firmware/application/gradient.hpp
similarity index 56%
rename from firmware/application/external/tetris/Arial12x12.h
rename to firmware/application/gradient.hpp
index 4dc451eeb..d32d58e59 100644
--- a/firmware/application/external/tetris/Arial12x12.h
+++ b/firmware/application/gradient.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 Mark Thompson
+ * Copyright (C) 2025 Belousov Oleg
*
* This file is part of PortaPack.
*
@@ -19,11 +19,33 @@
* Boston, MA 02110-1301, USA.
*/
-// dummy include file to avoid changing original source
+#ifndef __GRADIENT_H__
+#define __GRADIENT_H__
-#ifndef __UI_Arial12x12_H__
-#define __UI_Arial12x12_H__
+#include "ui.hpp"
+#include "file.hpp"
-#define Arial12x12 (0)
+#include
+#include
-#endif /*__UI_Arial12x12_H__*/
+extern const std::filesystem::path default_gradient_file;
+
+class Gradient {
+ public:
+ std::array lut{};
+
+ Gradient();
+
+ void set_default();
+ bool load_file(const std::filesystem::path& file_path);
+
+ private:
+ int16_t prev_index = 0;
+ int16_t prev_r = 0;
+ int16_t prev_g = 0;
+ int16_t prev_b = 0;
+
+ void step(int16_t index, int16_t r, int16_t g, int16_t b);
+};
+
+#endif /* __GRADIENT_H__ */
diff --git a/firmware/application/hw/debounce.cpp b/firmware/application/hw/debounce.cpp
index 31d812370..17905edb1 100644
--- a/firmware/application/hw/debounce.cpp
+++ b/firmware/application/hw/debounce.cpp
@@ -84,7 +84,7 @@ bool Debounce::feed(const uint8_t bit) {
// (by toggling reported state every 1/2 of the delay time)
if (--repeat_ctr_ == 0) {
state_to_report_ = !state_to_report_;
- repeat_ctr_ = REPEAT_SUBSEQUENT_DELAY / 2;
+ repeat_ctr_ = portapack::persistent_memory::ui_button_repeat_speed() ? REPEAT_SUBSEQUENT_DELAY_FAST / 2 : REPEAT_SUBSEQUENT_DELAY_NORMAL / 2;
return true;
}
}
@@ -96,7 +96,7 @@ bool Debounce::feed(const uint8_t bit) {
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
// indicating it was a LONG press
// (note that repeat_support and long_press support are mutually exclusive)
- if (held_time_ >= LONG_PRESS_DELAY) {
+ if (held_time_ >= (portapack::persistent_memory::ui_button_long_press_delay() ? LONG_PRESS_DELAY_FAST : LONG_PRESS_DELAY_NORMAL)) {
pulse_upon_release_ = false;
long_press_occurred_ = true;
state_to_report_ = 1;
@@ -104,7 +104,7 @@ bool Debounce::feed(const uint8_t bit) {
}
} else if (repeat_enabled_ && !long_press_enabled_) {
// Repeat support -- 4 directional buttons only (unless long_press is enabled)
- if (held_time_ >= REPEAT_INITIAL_DELAY) {
+ if (held_time_ >= (portapack::persistent_memory::ui_button_repeat_delay() ? REPEAT_INITIAL_DELAY_FAST : REPEAT_INITIAL_DELAY_NORMAL)) {
// Delay reached; trigger repeat code on NEXT tick
repeat_ctr_ = 1;
held_time_ = 0;
diff --git a/firmware/application/hw/debounce.hpp b/firmware/application/hw/debounce.hpp
index 1d042b1da..b40475588 100644
--- a/firmware/application/hw/debounce.hpp
+++ b/firmware/application/hw/debounce.hpp
@@ -30,9 +30,13 @@
#define DEBOUNCE_MASK ((1 << DEBOUNCE_COUNT) - 1)
// # of timer0 ticks before a held button starts being counted as repeated presses
-#define REPEAT_INITIAL_DELAY 250
-#define REPEAT_SUBSEQUENT_DELAY 92
-#define LONG_PRESS_DELAY 800
+#define REPEAT_INITIAL_DELAY_NORMAL 250
+#define REPEAT_SUBSEQUENT_DELAY_NORMAL 92
+#define LONG_PRESS_DELAY_NORMAL 800
+
+#define REPEAT_INITIAL_DELAY_FAST 188
+#define REPEAT_SUBSEQUENT_DELAY_FAST 66
+#define LONG_PRESS_DELAY_FAST 555
class Debounce {
public:
diff --git a/firmware/application/hw/encoder.cpp b/firmware/application/hw/encoder.cpp
index 352b70de6..b142d0543 100644
--- a/firmware/application/hw/encoder.cpp
+++ b/firmware/application/hw/encoder.cpp
@@ -66,6 +66,11 @@ int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
if (direction == prev_direction) {
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
return 0;
+
+ // false: normal, true: reverse
+ if (portapack::persistent_memory::encoder_dial_direction())
+ direction = -direction;
+
return direction;
}
diff --git a/firmware/application/log_file.cpp b/firmware/application/log_file.cpp
index 61c6bbd22..105fd7bad 100644
--- a/firmware/application/log_file.cpp
+++ b/firmware/application/log_file.cpp
@@ -28,10 +28,10 @@ Optional LogFile::write_entry(const std::string& entry) {
Optional LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
std::string timestamp = to_string_timestamp(datetime);
- return write_line(timestamp + " " + entry);
+ return write_raw(timestamp + " " + entry);
}
-Optional LogFile::write_line(const std::string& message) {
+Optional LogFile::write_raw(const std::string& message) {
auto error = file.write_line(message);
if (!error) {
file.sync();
diff --git a/firmware/application/log_file.hpp b/firmware/application/log_file.hpp
index 430bf14ae..423552e38 100644
--- a/firmware/application/log_file.hpp
+++ b/firmware/application/log_file.hpp
@@ -39,11 +39,10 @@ class LogFile {
Optional write_entry(const std::string& entry);
Optional write_entry(const rtc::RTC& datetime, const std::string& entry);
+ Optional write_raw(const std::string& message);
private:
File file{};
-
- Optional write_line(const std::string& message);
};
#endif /*__LOG_FILE_H__*/
diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp
index 251a265ad..16ab3af8f 100644
--- a/firmware/application/portapack.cpp
+++ b/firmware/application/portapack.cpp
@@ -538,7 +538,6 @@ init_status_t init() {
set_cpu_clock_speed();
- if (persistent_memory::config_lcd_inverted_mode()) display.set_inverted(true);
/* sample max: 1023 sample_t AKA uint16_t
* touch_sensitivity: range: 1 to 128
* threshold range: 1023/1 to 1023/128 = 1023 to 8
diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp
index f7c795555..d08249271 100644
--- a/firmware/application/receiver_model.cpp
+++ b/firmware/application/receiver_model.cpp
@@ -39,13 +39,22 @@ using namespace portapack;
namespace {
-static constexpr std::array am_configs{{
+static constexpr std::array am_configs{{
// we config here all the non COMMON parameters to each AM modulation type in RX.
- {taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
- {taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
- {taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 2K8 (+ 2K8)
- {taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB}, // SSB LSB BW 2K8 (- 2K8)
- {taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 0K7 (+ 0K7) used to get audio tone from CW Morse, assuming tx shifted +700hz aprox
+ {taps_6k0_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
+ {taps_6k0_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
+ {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
+ {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
+ {taps_6k0_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
+ {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax.
+
+ // below options for Waterfall zoom x 2
+ {taps_6k0_narrow_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
+ {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
+ {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
+ {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
+ {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
+ {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax with waterfall zoom x 2 (we need taps_6k0_narrow_decim_1 to minimize aliasing)
}};
static constexpr std::array nbfm_configs{{
@@ -57,7 +66,13 @@ static constexpr std::array nbfm_configs{{
static constexpr std::array wfm_configs{{
{taps_200k_wfm_decim_0, taps_200k_wfm_decim_1},
{taps_180k_wfm_decim_0, taps_180k_wfm_decim_1},
- {taps_40k_wfm_decim_0, taps_40k_wfm_decim_1},
+ {taps_80k_wfm_decim_0, taps_80k_wfm_decim_1},
+}};
+
+static constexpr std::array wfmam_configs{{
+ {taps_16k0_decim_0, taps_80k_wfmam_decim_1, taps_64_lp_1875_2166},
+ {taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_lp_1875_2166},
+ {taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_bpf_2k4_bw_2k},
}};
} /* namespace */
@@ -145,6 +160,17 @@ void ReceiverModel::set_am_configuration(uint8_t n) {
}
}
+uint8_t ReceiverModel::amfm_configuration() const {
+ return settings_.amfm_config_index;
+}
+
+void ReceiverModel::set_amfm_configuration(uint8_t n) {
+ if (n < am_configs.size()) {
+ settings_.amfm_config_index = n;
+ update_modulation();
+ }
+}
+
uint8_t ReceiverModel::nbfm_configuration() const {
return settings_.nbfm_config_index;
}
@@ -167,6 +193,17 @@ void ReceiverModel::set_wfm_configuration(uint8_t n) {
}
}
+uint8_t ReceiverModel::wfmam_configuration() const {
+ return settings_.wfmam_config_index;
+}
+
+void ReceiverModel::set_wfmam_configuration(uint8_t n) {
+ if (n < wfmam_configs.size()) {
+ settings_.wfmam_config_index = n;
+ update_modulation();
+ }
+}
+
uint8_t ReceiverModel::squelch_level() const {
return settings_.squelch_level;
}
@@ -238,12 +275,14 @@ void ReceiverModel::set_configuration_without_update(
size_t new_am_config_index,
size_t new_nbfm_config_index,
size_t new_wfm_config_index,
+ size_t new_wfmam_config_index,
uint8_t new_squelch_level) {
settings_.mode = new_mode;
settings_.frequency_step = new_frequency_step;
settings_.am_config_index = new_am_config_index;
settings_.nbfm_config_index = new_nbfm_config_index;
settings_.wfm_config_index = new_wfm_config_index;
+ settings_.wfmam_config_index = new_wfmam_config_index;
settings_.squelch_level = new_squelch_level;
}
@@ -267,7 +306,12 @@ int32_t ReceiverModel::tuning_offset() {
void ReceiverModel::update_tuning_frequency() {
// TODO: use positive offset if freq < offset.
- radio::set_tuning_frequency(target_frequency() + tuning_offset());
+ radio::set_tuning_frequency(target_frequency() + hidden_offset + tuning_offset());
+}
+
+void ReceiverModel::set_hidden_offset(rf::Frequency offset) {
+ hidden_offset = offset;
+ update_tuning_frequency();
}
void ReceiverModel::update_baseband_bandwidth() {
@@ -303,6 +347,10 @@ void ReceiverModel::update_modulation() {
update_am_configuration();
break;
+ case Mode::AMAudioFMApt: // Wefax , first step , USB demodulation from the AMAudio group, index 2 (USB+3K), TODO +FM subcarrier demod ?
+ update_amfm_configuration();
+ break;
+
case Mode::NarrowbandFMAudio:
update_nbfm_configuration();
break;
@@ -311,6 +359,10 @@ void ReceiverModel::update_modulation() {
update_wfm_configuration();
break;
+ case Mode::WFMAudioAMApt:
+ update_wfmam_configuration();
+ break;
+
case Mode::SpectrumAnalysis:
case Mode::Capture:
break;
@@ -321,6 +373,10 @@ void ReceiverModel::update_am_configuration() {
am_configs[am_configuration()].apply();
}
+void ReceiverModel::update_amfm_configuration() {
+ am_configs[amfm_configuration()].apply(); // update with different index for Wefax.
+}
+
void ReceiverModel::update_nbfm_configuration() {
nbfm_configs[nbfm_configuration()].apply(squelch_level());
}
@@ -329,6 +385,10 @@ void ReceiverModel::update_wfm_configuration() {
wfm_configs[wfm_configuration()].apply();
}
+void ReceiverModel::update_wfmam_configuration() {
+ wfmam_configs[wfmam_configuration()].apply(); // update with different index for Wefax.
+}
+
void ReceiverModel::update_antenna_bias() {
if (enabled_)
radio::set_antenna_bias(portapack::get_antenna_bias());
diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp
index ecdb32236..ea9317bf5 100644
--- a/firmware/application/receiver_model.hpp
+++ b/firmware/application/receiver_model.hpp
@@ -41,7 +41,9 @@ class ReceiverModel {
NarrowbandFMAudio = 1,
WidebandFMAudio = 2,
SpectrumAnalysis = 3,
- Capture = 4
+ AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
+ WFMAudioAMApt = 5, // Added to handle SAT Weather map , NOAA 137 Mhz.
+ Capture = 6,
};
struct settings_t {
@@ -54,6 +56,8 @@ class ReceiverModel {
bool rf_amp = false;
Mode mode = Mode::NarrowbandFMAudio;
uint8_t am_config_index = 0;
+ uint8_t amfm_config_index = 0;
+ uint8_t wfmam_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch_level = 80;
@@ -87,12 +91,18 @@ class ReceiverModel {
uint8_t am_configuration() const;
void set_am_configuration(uint8_t n);
+ uint8_t amfm_configuration() const;
+ void set_amfm_configuration(uint8_t n);
+
uint8_t nbfm_configuration() const;
void set_nbfm_configuration(uint8_t n);
uint8_t wfm_configuration() const;
void set_wfm_configuration(uint8_t n);
+ uint8_t wfmam_configuration() const;
+ void set_wfmam_configuration(uint8_t n);
+
uint8_t squelch_level() const;
void set_squelch_level(uint8_t v);
@@ -105,6 +115,8 @@ class ReceiverModel {
uint8_t normalized_headphone_volume() const;
void set_normalized_headphone_volume(uint8_t v);
+ void set_hidden_offset(rf::Frequency offset);
+
void enable();
void disable();
@@ -117,6 +129,7 @@ class ReceiverModel {
size_t new_am_config_index,
size_t new_nbfm_config_index,
size_t new_wfm_config_index,
+ size_t new_wfmam_config_index,
uint8_t new_squelch_level);
void configure_from_app_settings(const app_settings::AppSettings& settings);
@@ -128,6 +141,7 @@ class ReceiverModel {
private:
settings_t settings_{};
bool enabled_ = false;
+ rf::Frequency hidden_offset = 0; // when we need to hide the offset from user, we set this. like when WeFax needs -300Hz.
int32_t tuning_offset();
@@ -140,8 +154,10 @@ class ReceiverModel {
void update_modulation();
void update_am_configuration();
+ void update_amfm_configuration();
void update_nbfm_configuration();
void update_wfm_configuration();
+ void update_wfmam_configuration();
void update_antenna_bias();
void update_headphone_volume();
diff --git a/firmware/application/recent_entries.cpp b/firmware/application/recent_entries.cpp
index 2cbb7d9bf..8f6abb95b 100644
--- a/firmware/application/recent_entries.cpp
+++ b/firmware/application/recent_entries.cpp
@@ -24,7 +24,7 @@
namespace ui {
RecentEntriesColumns::RecentEntriesColumns(
- const std::initializer_list columns)
+ std::initializer_list columns)
: _columns{columns} {
}
diff --git a/firmware/application/recent_entries.hpp b/firmware/application/recent_entries.hpp
index 83e6189bf..b5a33fec1 100644
--- a/firmware/application/recent_entries.hpp
+++ b/firmware/application/recent_entries.hpp
@@ -133,14 +133,27 @@ class RecentEntriesColumns {
public:
using ContainerType = std::vector;
- RecentEntriesColumns(
- const std::initializer_list columns);
+ RecentEntriesColumns(std::initializer_list columns);
- ContainerType::const_iterator begin() const { return std::begin(_columns); }
- ContainerType::const_iterator end() const { return std::end(_columns); }
+ ContainerType::iterator begin() { return _columns.begin(); }
+ ContainerType::iterator end() { return _columns.end(); }
+ ContainerType::const_iterator begin() const { return _columns.begin(); }
+ ContainerType::const_iterator end() const { return _columns.end(); }
+
+ void set(size_t index, const std::string& name, size_t width) {
+ if (index < _columns.size()) {
+ _columns[index] = {name, width};
+ }
+ }
+
+ const RecentEntriesColumn& at(size_t index) const {
+ return _columns.at(index);
+ }
+
+ size_t size() const { return _columns.size(); }
private:
- const ContainerType _columns;
+ ContainerType _columns;
};
class RecentEntriesHeader : public Widget {
diff --git a/firmware/application/spectrum_color_lut.cpp b/firmware/application/spectrum_color_lut.cpp
index a2877542e..832fa9756 100644
--- a/firmware/application/spectrum_color_lut.cpp
+++ b/firmware/application/spectrum_color_lut.cpp
@@ -280,265 +280,6 @@ const std::array spectrum_rgb2_lut{{
{132, 0, 0},
}};
-const std::array spectrum_rgb3_lut{{
- {0, 0, 0},
- {0, 0, 3},
- {0, 0, 6},
- {0, 0, 9},
- {0, 0, 12},
- {0, 0, 15},
- {0, 0, 18},
- {0, 0, 21},
- {0, 0, 24},
- {0, 0, 27},
- {0, 0, 30},
- {0, 0, 33},
- {0, 0, 36},
- {0, 0, 39},
- {0, 0, 42},
- {0, 0, 45},
- {0, 0, 48},
- {0, 0, 51},
- {0, 0, 54},
- {0, 0, 57},
- {0, 0, 60},
- {0, 0, 63},
- {0, 0, 66},
- {0, 0, 69},
- {0, 0, 72},
- {0, 0, 75},
- {0, 0, 78},
- {0, 0, 81},
- {0, 0, 84},
- {0, 0, 87},
- {0, 0, 90},
- {0, 0, 93},
- {0, 0, 96},
- {0, 0, 99},
- {0, 0, 102},
- {0, 0, 105},
- {0, 0, 108},
- {0, 0, 111},
- {0, 0, 114},
- {0, 0, 117},
- {0, 0, 120},
- {0, 0, 123},
- {0, 0, 126},
- {0, 0, 129},
- {0, 0, 132},
- {0, 0, 135},
- {0, 0, 138},
- {0, 0, 141},
- {0, 0, 144},
- {0, 0, 147},
- {0, 0, 150},
- {0, 0, 153},
- {0, 0, 156},
- {0, 0, 159},
- {0, 0, 162},
- {0, 0, 165},
- {0, 0, 168},
- {0, 0, 171},
- {0, 0, 174},
- {0, 0, 177},
- {0, 0, 180},
- {0, 0, 183},
- {0, 0, 186},
- {0, 0, 189},
- {0, 0, 192},
- {0, 0, 195},
- {0, 0, 198},
- {0, 0, 201},
- {0, 0, 204},
- {0, 0, 207},
- {0, 0, 210},
- {0, 0, 213},
- {0, 0, 216},
- {0, 0, 219},
- {0, 0, 222},
- {0, 0, 225},
- {0, 0, 228},
- {0, 0, 231},
- {0, 0, 234},
- {0, 0, 237},
- {0, 0, 240},
- {0, 0, 243},
- {0, 0, 246},
- {0, 0, 249},
- {0, 0, 252},
- {0, 0, 255},
- {0, 3, 252},
- {0, 6, 249},
- {0, 9, 246},
- {0, 12, 243},
- {0, 15, 240},
- {0, 18, 237},
- {0, 21, 234},
- {0, 24, 231},
- {0, 27, 228},
- {0, 30, 225},
- {0, 33, 222},
- {0, 36, 219},
- {0, 39, 216},
- {0, 42, 213},
- {0, 45, 210},
- {0, 48, 207},
- {0, 51, 204},
- {0, 54, 201},
- {0, 57, 198},
- {0, 60, 195},
- {0, 63, 192},
- {0, 66, 189},
- {0, 69, 186},
- {0, 72, 183},
- {0, 75, 180},
- {0, 78, 177},
- {0, 81, 174},
- {0, 84, 171},
- {0, 87, 168},
- {0, 90, 165},
- {0, 93, 162},
- {0, 96, 159},
- {0, 99, 156},
- {0, 102, 153},
- {0, 105, 150},
- {0, 108, 147},
- {0, 111, 144},
- {0, 114, 141},
- {0, 117, 138},
- {0, 120, 135},
- {0, 123, 132},
- {0, 126, 129},
- {0, 129, 126},
- {0, 132, 123},
- {0, 135, 120},
- {0, 138, 117},
- {0, 141, 114},
- {0, 144, 111},
- {0, 147, 108},
- {0, 150, 105},
- {0, 153, 102},
- {0, 156, 99},
- {0, 159, 96},
- {0, 162, 93},
- {0, 165, 90},
- {0, 168, 87},
- {0, 171, 84},
- {0, 174, 81},
- {0, 177, 78},
- {0, 180, 75},
- {0, 183, 72},
- {0, 186, 69},
- {0, 189, 66},
- {0, 192, 63},
- {0, 195, 60},
- {0, 198, 57},
- {0, 201, 54},
- {0, 204, 51},
- {0, 207, 48},
- {0, 210, 45},
- {0, 213, 42},
- {0, 216, 39},
- {0, 219, 36},
- {0, 222, 33},
- {0, 225, 30},
- {0, 228, 27},
- {0, 231, 24},
- {0, 234, 21},
- {0, 237, 18},
- {0, 240, 15},
- {0, 243, 12},
- {0, 246, 9},
- {0, 249, 6},
- {0, 252, 3},
- {0, 255, 0},
- {3, 252, 0},
- {6, 249, 0},
- {9, 246, 0},
- {12, 243, 0},
- {15, 240, 0},
- {18, 237, 0},
- {21, 234, 0},
- {24, 231, 0},
- {27, 228, 0},
- {30, 225, 0},
- {33, 222, 0},
- {36, 219, 0},
- {39, 216, 0},
- {42, 213, 0},
- {45, 210, 0},
- {48, 207, 0},
- {51, 204, 0},
- {54, 201, 0},
- {57, 198, 0},
- {60, 195, 0},
- {63, 192, 0},
- {66, 189, 0},
- {69, 186, 0},
- {72, 183, 0},
- {75, 180, 0},
- {78, 177, 0},
- {81, 174, 0},
- {84, 171, 0},
- {87, 168, 0},
- {90, 165, 0},
- {93, 162, 0},
- {96, 159, 0},
- {99, 156, 0},
- {102, 153, 0},
- {105, 150, 0},
- {108, 147, 0},
- {111, 144, 0},
- {114, 141, 0},
- {117, 138, 0},
- {120, 135, 0},
- {123, 132, 0},
- {126, 129, 0},
- {129, 126, 0},
- {132, 123, 0},
- {135, 120, 0},
- {138, 117, 0},
- {141, 114, 0},
- {144, 111, 0},
- {147, 108, 0},
- {150, 105, 0},
- {153, 102, 0},
- {156, 99, 0},
- {159, 96, 0},
- {162, 93, 0},
- {165, 90, 0},
- {168, 87, 0},
- {171, 84, 0},
- {174, 81, 0},
- {177, 78, 0},
- {180, 75, 0},
- {183, 72, 0},
- {186, 69, 0},
- {189, 66, 0},
- {192, 63, 0},
- {195, 60, 0},
- {198, 57, 0},
- {201, 54, 0},
- {204, 51, 0},
- {207, 48, 0},
- {210, 45, 0},
- {213, 42, 0},
- {216, 39, 0},
- {219, 36, 0},
- {222, 33, 0},
- {225, 30, 0},
- {228, 27, 0},
- {231, 24, 0},
- {234, 21, 0},
- {237, 18, 0},
- {240, 15, 0},
- {243, 12, 0},
- {246, 9, 0},
- {249, 6, 0},
- {252, 3, 0},
- {255, 0, 0},
-}};
-
const std::array spectrum_rgb4_lut{{
{0, 0, 0},
{1, 1, 1},
diff --git a/firmware/application/spectrum_color_lut.hpp b/firmware/application/spectrum_color_lut.hpp
index 1516ae3b1..0e748c33d 100644
--- a/firmware/application/spectrum_color_lut.hpp
+++ b/firmware/application/spectrum_color_lut.hpp
@@ -27,7 +27,6 @@
#include
extern const std::array spectrum_rgb2_lut;
-extern const std::array spectrum_rgb3_lut;
extern const std::array spectrum_rgb4_lut;
#endif /*__SPECTRUM_COLOR_LUT_H__*/
diff --git a/firmware/application/theme.cpp b/firmware/application/theme.cpp
index 7744d6c37..22b42f887 100644
--- a/firmware/application/theme.cpp
+++ b/firmware/application/theme.cpp
@@ -1,4 +1,3 @@
-
#include "theme.hpp"
namespace ui {
@@ -25,6 +24,9 @@ void Theme::SetTheme(ThemeId theme) {
case Red:
current = new ThemeRed();
break;
+ case Dark:
+ current = new ThemeDark();
+ break;
case DefaultGrey:
default:
current = new ThemeDefault();
@@ -725,4 +727,137 @@ ThemeRed::ThemeRed() {
bg_table_header = new Color{205, 30, 0};
}
+ThemeDark::ThemeDark() {
+ bg_lightest = new Style{
+ .font = font::fixed_8x16,
+ .background = {32, 32, 32},
+ .foreground = Color::white(),
+ };
+ bg_lightest_small = new Style{
+ .font = font::fixed_5x8,
+ .background = {32, 32, 32},
+ .foreground = Color::white(),
+ };
+ bg_light = new Style{
+ .font = font::fixed_8x16,
+ .background = {24, 24, 24},
+ .foreground = Color::white(),
+ };
+ bg_medium = new Style{
+ .font = font::fixed_8x16,
+ .background = {16, 16, 16},
+ .foreground = Color::white(),
+ };
+ bg_dark = new Style{
+ .font = font::fixed_8x16,
+ .background = {8, 8, 8},
+ .foreground = Color::white(),
+ };
+ bg_darker = new Style{
+ .font = font::fixed_8x16,
+ .background = {4, 4, 4},
+ .foreground = Color::white(),
+ };
+
+ bg_darkest = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::white(),
+ };
+ bg_darkest_small = new Style{
+ .font = font::fixed_5x8,
+ .background = Color::black(),
+ .foreground = Color::white(),
+ };
+
+ bg_important_small = new Style{
+ .font = font::fixed_5x8,
+ .background = {64, 64, 64},
+ .foreground = Color::white(),
+ };
+
+ error_dark = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::red(),
+ };
+ warning_dark = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::yellow(),
+ };
+ ok_dark = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::green(),
+ };
+
+ fg_dark = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = {96, 96, 96},
+ };
+ fg_medium = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = {128, 128, 128},
+ };
+ fg_light = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::white(),
+ };
+
+ fg_red = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::red(),
+ };
+ fg_green = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::green(),
+ };
+ fg_yellow = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::yellow(),
+ };
+ fg_orange = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::orange(),
+ };
+ fg_blue = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::blue(),
+ };
+ fg_cyan = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::cyan(),
+ };
+ fg_darkcyan = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::dark_cyan(),
+ };
+ fg_magenta = new Style{
+ .font = font::fixed_8x16,
+ .background = Color::black(),
+ .foreground = Color::magenta(),
+ };
+
+ option_active = new Style{
+ .font = font::fixed_8x16,
+ .background = {64, 64, 64},
+ .foreground = Color::white(),
+ };
+
+ status_active = new Color{0, 255, 0};
+
+ bg_table_header = new Color{48, 48, 48};
+}
+
} // namespace ui
\ No newline at end of file
diff --git a/firmware/application/theme.hpp b/firmware/application/theme.hpp
index da6ccc9ff..b4ecf0973 100644
--- a/firmware/application/theme.hpp
+++ b/firmware/application/theme.hpp
@@ -93,6 +93,11 @@ class ThemeRed : public ThemeTemplate {
ThemeRed();
};
+class ThemeDark : public ThemeTemplate {
+ public:
+ ThemeDark();
+};
+
class Theme {
public:
enum ThemeId {
@@ -101,6 +106,7 @@ class Theme {
Aqua = 2,
Green = 3,
Red = 4,
+ Dark = 5,
MAX
};
static ThemeTemplate* getInstance();
diff --git a/firmware/application/ui/external/readme.md b/firmware/application/ui/external/readme.md
new file mode 100644
index 000000000..0d7a84abe
--- /dev/null
+++ b/firmware/application/ui/external/readme.md
@@ -0,0 +1,43 @@
+
+Copyright (C) 2025 HTotoo
+
+
+# External UI elements
+
+
+## Read carefully
+
+
+These widgets are only used in external apps!
+The concept is, we move these widgets out of FW space, and compile them with external apps. To all separately. This is multiple include, but since we compile the widget under different namespaces (and those namespaces will be under the external_app namespace) these all will be removed from the base firmware.
+
+This way we can free up some spaces, and still reuse the same widgets in different apps easily.
+
+## How to create external widget
+
+You create a **hpp** file indet the ui/external folder, and include everything you need for your widget as you normally wanted to do.
+**Don't forget the ifdef guards!**
+**Never use any namespace for your class there!** The namespace of the actual ext app will be used where you include this.
+
+Then create a **cpi** file, where you include your hpp file, and create the implementation of your widget class. Still, don't use namespace.
+
+You won't need to include these files in any cmake file! (see usage, why)
+
+## How to use these widgets
+
+This is important, so follow the rules carefully. If it works, doesn't mean, you did it good!
+In your external app's hpp file, you need to include the widget's hpp file.
+**But be careful**, you must include it under the ext app's namespace, before your first class. For examlpe:
+
+ namespace ui::external_app::gfxeq {
+ #include "external/ui_grapheq.hpp"
+ class gfxEQView : public View {
+Then you must include the cpi file in the external app's cpp file. Again, it must be under the ext app's namespace!
+
+ using namespace portapack;
+ namespace ui::external_app::gfxeq {
+ #include "external/ui_grapheq.cpi"
+ gfxEQView::gfxEQView(NavigationView& nav)
+
+This must be done under each ext app that uses the same widget.
+This way, the widget will be compiled multiple times under the external_app namespace, but those will be removed from the base. Each app will be able to use it, easy to handle, easy to create.
\ No newline at end of file
diff --git a/firmware/application/ui/external/ui_grapheq.cpi b/firmware/application/ui/external/ui_grapheq.cpi
new file mode 100644
index 000000000..6629bc256
--- /dev/null
+++ b/firmware/application/ui/external/ui_grapheq.cpi
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2025 RocketGod
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_grapheq.hpp"
+
+/* GraphEq *************************************************************/
+
+GraphEq::GraphEq(
+ Rect parent_rect,
+ bool clickable)
+ : Widget{parent_rect},
+ clickable_{clickable},
+ bar_heights(NUM_BARS, 0),
+ prev_bar_heights(NUM_BARS, 0) {
+ if (clickable) {
+ set_focusable(true);
+ // previous_data.resize(length_, 0);
+ }
+}
+
+void GraphEq::set_parent_rect(const Rect new_parent_rect) {
+ Widget::set_parent_rect(new_parent_rect);
+ calculate_params();
+}
+
+void GraphEq::calculate_params() {
+ y_top = screen_rect().top();
+ RENDER_HEIGHT = parent_rect().height();
+ BAR_WIDTH = (parent_rect().width() - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
+ HORIZONTAL_OFFSET = screen_rect().left();
+}
+
+bool GraphEq::is_paused() const {
+ return paused_;
+}
+
+void GraphEq::set_paused(bool paused) {
+ paused_ = paused;
+ needs_background_redraw = true;
+ set_dirty();
+}
+
+bool GraphEq::is_clickable() const {
+ return clickable_;
+}
+
+void GraphEq::getAccessibilityText(std::string& result) {
+ result = paused_ ? "paused GraphEq" : "GraphEq";
+}
+
+void GraphEq::getWidgetName(std::string& result) {
+ result = "GraphEq";
+}
+
+bool GraphEq::on_key(const KeyEvent key) {
+ if (!clickable_) return false;
+
+ if (key == KeyEvent::Select) {
+ set_paused(!paused_);
+ if (on_select) {
+ on_select(*this);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool GraphEq::on_keyboard(const KeyboardEvent key) {
+ if (!clickable_) return false;
+
+ if (key == 32 || key == 10) {
+ set_paused(!paused_);
+ if (on_select) {
+ on_select(*this);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool GraphEq::on_touch(const TouchEvent event) {
+ if (!clickable_) return false;
+
+ switch (event.type) {
+ case TouchEvent::Type::Start:
+ focus();
+ return true;
+
+ case TouchEvent::Type::End:
+ set_paused(!paused_);
+ if (on_select) {
+ on_select(*this);
+ }
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void GraphEq::set_theme(Color base_color_, Color peak_color_) {
+ base_color = base_color_;
+ peak_color = peak_color_;
+ set_dirty();
+}
+
+void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
+ const float bin_frequency_size = 48000.0f / 128;
+
+ for (int bar = 0; bar < NUM_BARS; bar++) {
+ float start_freq = FREQUENCY_BANDS[bar];
+ float end_freq = FREQUENCY_BANDS[bar + 1];
+
+ int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
+ int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
+
+ if (start_bin >= end_bin) {
+ end_bin = start_bin + 1;
+ }
+
+ float total_energy = 0;
+ int bin_count = 0;
+
+ for (int bin = start_bin; bin <= end_bin; bin++) {
+ total_energy += spectrum.db[bin];
+ bin_count++;
+ }
+
+ float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
+
+ // Manually boost highs for better visual balance
+ float treble_boost = 1.0f;
+ if (bar == 10)
+ treble_boost = 1.7f;
+ else if (bar >= 9)
+ treble_boost = 1.3f;
+ else if (bar >= 7)
+ treble_boost = 1.3f;
+
+ // Mid emphasis for a V-shape effect
+ float mid_boost = 1.0f;
+ if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
+
+ float amplified_db = avg_db * treble_boost * mid_boost;
+
+ if (amplified_db > 255) amplified_db = 255;
+
+ float band_scale = 1.0f;
+ int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
+
+ if (target_height > RENDER_HEIGHT) {
+ target_height = RENDER_HEIGHT;
+ }
+
+ // Adjusted to look nice to my eyes
+ float rise_speed = 0.8f;
+ float fall_speed = 1.0f;
+
+ if (target_height > bar_heights[bar]) {
+ bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
+ } else {
+ bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
+ }
+ }
+ set_dirty();
+}
+
+void GraphEq::paint(Painter& painter) {
+ if (!visible()) return;
+ if (!is_calculated) { // calc positions first
+ calculate_params();
+ is_calculated = true;
+ }
+ if (needs_background_redraw) {
+ painter.fill_rectangle(screen_rect(), Theme::getInstance()->bg_darkest->background);
+ needs_background_redraw = false;
+ }
+ if (paused_) {
+ return;
+ }
+ const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
+ uint16_t bottom = screen_rect().bottom();
+ for (int bar = 0; bar < NUM_BARS; bar++) {
+ int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
+ int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
+
+ if (prev_bar_heights[bar] > active_segments) {
+ int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
+ int clear_y = bottom - prev_bar_heights[bar] * SEGMENT_HEIGHT;
+ painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Theme::getInstance()->bg_darkest->background);
+ }
+
+ for (int seg = 0; seg < active_segments; seg++) {
+ int y = bottom - (seg + 1) * SEGMENT_HEIGHT;
+ if (y < y_top) break;
+
+ Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? peak_color : base_color;
+ painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
+ }
+ prev_bar_heights[bar] = active_segments;
+ }
+}
diff --git a/firmware/application/ui/external/ui_grapheq.hpp b/firmware/application/ui/external/ui_grapheq.hpp
new file mode 100644
index 000000000..e8d326a0b
--- /dev/null
+++ b/firmware/application/ui/external/ui_grapheq.hpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 RocketGod
+ * Copyright (C) 2025 HTotoo
+ *
+ * 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_GRAPHEQ_H__
+#define __UI_GRAPHEQ_H__
+
+#include "ui_widget.hpp"
+
+class GraphEq : public Widget {
+ public:
+ std::function on_select{};
+
+ GraphEq(Rect parent_rect, bool clickable = false);
+ GraphEq(const GraphEq&) = delete;
+ GraphEq(GraphEq&&) = delete;
+ GraphEq& operator=(const GraphEq&) = delete;
+ GraphEq& operator=(GraphEq&&) = delete;
+
+ bool is_paused() const;
+ void set_paused(bool paused);
+ bool is_clickable() const;
+
+ void paint(Painter& painter) override;
+ bool on_key(const KeyEvent key) override;
+ bool on_touch(const TouchEvent event) override;
+ bool on_keyboard(const KeyboardEvent event) override;
+ void set_parent_rect(const Rect new_parent_rect) override;
+
+ void getAccessibilityText(std::string& result) override;
+ void getWidgetName(std::string& result) override;
+ void update_audio_spectrum(const AudioSpectrum& spectrum);
+ void set_theme(Color base_color, Color peak_color);
+
+ private:
+ bool is_calculated{false};
+ bool paused_{false};
+ bool clickable_{false};
+ bool needs_background_redraw{true}; // Redraw background only when needed.
+ Color base_color = Color(255, 0, 255);
+ Color peak_color = Color(255, 255, 255);
+ std::vector bar_heights;
+ std::vector prev_bar_heights;
+
+ ui::Dim y_top = 2 * 16;
+ ui::Dim RENDER_HEIGHT = 288;
+ ui::Dim BAR_WIDTH = 20;
+ ui::Dim HORIZONTAL_OFFSET = 2;
+ static const int NUM_BARS = 11;
+ static const int BAR_SPACING = 2;
+ static const int SEGMENT_HEIGHT = 10;
+ static constexpr std::array FREQUENCY_BANDS = {
+ 375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
+ 750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
+ 1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
+ 2250, // Midrange clarity (e.g., vocal presence, snare crack)
+ 3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
+ 4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
+ 6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
+ 9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
+ 13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
+ 16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
+ 20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
+ 24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
+ };
+
+ void calculate_params(); // re calculate some parameters based on parent_rect()
+};
+
+#endif /*__UI_GRAPHEQ_H__*/
\ No newline at end of file
diff --git a/firmware/application/ui/ui_alphanum.cpp b/firmware/application/ui/ui_alphanum.cpp
index e58fe0885..b072a76be 100644
--- a/firmware/application/ui/ui_alphanum.cpp
+++ b/firmware/application/ui/ui_alphanum.cpp
@@ -32,7 +32,8 @@ namespace ui {
AlphanumView::AlphanumView(
NavigationView& nav,
std::string& str,
- size_t max_length)
+ size_t max_length,
+ uint8_t enter_mode)
: TextEntryView(nav, str, max_length) {
size_t n;
@@ -76,7 +77,7 @@ AlphanumView::AlphanumView(
n++;
}
- set_mode(mode);
+ set_mode(enter_mode);
button_mode.on_select = [this](Button&) {
set_mode(mode + 1);
diff --git a/firmware/application/ui/ui_alphanum.hpp b/firmware/application/ui/ui_alphanum.hpp
index 3ae045fef..654ed57e1 100644
--- a/firmware/application/ui/ui_alphanum.hpp
+++ b/firmware/application/ui/ui_alphanum.hpp
@@ -37,7 +37,7 @@ namespace ui {
class AlphanumView : public TextEntryView {
public:
- AlphanumView(NavigationView& nav, std::string& str, size_t max_length);
+ AlphanumView(NavigationView& nav, std::string& str, size_t max_length, uint8_t enter_mode);
AlphanumView(const AlphanumView&) = delete;
AlphanumView(AlphanumView&&) = delete;
diff --git a/firmware/application/ui/ui_btngrid.cpp b/firmware/application/ui/ui_btngrid.cpp
index d9646c6a2..de5114c94 100644
--- a/firmware/application/ui/ui_btngrid.cpp
+++ b/firmware/application/ui/ui_btngrid.cpp
@@ -38,17 +38,31 @@ BtnGridView::BtnGridView(
set_parent_rect(new_parent_rect);
set_focusable(true);
- signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
- this->on_tick_second();
+ button_pgup.set_focusable(false);
+ button_pgup.on_select = [this](Button&) {
+ if (arrow_up_enabled) {
+ if (((int64_t)highlighted_item - displayed_max) > 0)
+ set_highlighted(highlighted_item - displayed_max);
+ else
+ set_highlighted(0);
+ }
};
- add_child(&arrow_more);
- arrow_more.set_focusable(false);
- arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background);
+ button_pgdown.set_focusable(false);
+ button_pgdown.on_select = [this](Button&) {
+ if (arrow_down_enabled) {
+ set_highlighted(highlighted_item + displayed_max);
+ }
+ };
+
+ button_pgup.set_style(Theme::getInstance()->bg_darkest_small);
+ button_pgdown.set_style(Theme::getInstance()->bg_darkest_small);
+
+ add_child(&button_pgup);
+ add_child(&button_pgdown);
}
BtnGridView::~BtnGridView() {
- rtc_time::signal_tick_second -= signal_token_tick_second;
}
void BtnGridView::set_max_rows(int rows) {
@@ -62,8 +76,31 @@ int BtnGridView::rows() {
void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
+ button_h = 48; // btn_h_min;
+ /*
+ // DISABLED FOR NOW. TODO fix next, prev button pos
+ int min_remainder = parent_rect().size().height();
+ uint8_t max_button_count = 0;
+
+ for (int h = btn_h_min; h <= btn_h_max; ++h) {
+ int count = parent_rect().size().height() / h;
+ int remainder = parent_rect().size().height() % h;
+
+ // Prefer smaller remainder, then more buttons, then larger height
+ if (remainder < min_remainder ||
+ (remainder == min_remainder && count > max_button_count) ||
+ (remainder == min_remainder && count == max_button_count && h > button_h)) {
+ button_h = h;
+ min_remainder = remainder;
+ max_button_count = count;
+ }
+ }
+ */
displayed_max = (parent_rect().size().height() / button_h);
- arrow_more.set_parent_rect({228, (Coord)(displayed_max * button_h), 8, 8});
+
+ button_pgup.set_parent_rect({0, (Coord)(displayed_max * button_h), screen_width / 2, 16});
+ button_pgdown.set_parent_rect({screen_width / 2, (Coord)(displayed_max * button_h), screen_width / 2, 16});
+
displayed_max *= rows_;
// Delete any existing buttons.
@@ -84,28 +121,40 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
menu_item_views.push_back(std::move(item));
}
-
update_items();
}
-void BtnGridView::set_arrow_enabled(bool enabled) {
+void BtnGridView::set_arrow_up_enabled(bool enabled) {
+ if (!show_arrows)
+ return;
if (enabled) {
- add_child(&arrow_more);
- } else {
- remove_child(&arrow_more);
+ if (!arrow_up_enabled) {
+ arrow_up_enabled = true;
+ button_pgup.set_text("< PREV");
+ }
+ } else if (!enabled) {
+ if (arrow_up_enabled) {
+ arrow_up_enabled = false;
+ button_pgup.set_text(" ");
+ }
}
};
-void BtnGridView::on_tick_second() {
- if (more && blink)
- arrow_more.set_foreground(Theme::getInstance()->bg_darkest->foreground);
- else
- arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background);
-
- blink = !blink;
-
- arrow_more.set_dirty();
-}
+void BtnGridView::set_arrow_down_enabled(bool enabled) {
+ if (!show_arrows)
+ return;
+ if (enabled) {
+ if (!arrow_down_enabled) {
+ arrow_down_enabled = true;
+ button_pgdown.set_text("NEXT >");
+ }
+ } else if (!enabled) {
+ if (arrow_down_enabled) {
+ arrow_down_enabled = false;
+ button_pgdown.set_text(" ");
+ }
+ }
+};
void BtnGridView::clear() {
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
@@ -153,15 +202,23 @@ void BtnGridView::insert_item(const GridItem& new_item, size_t position, bool in
}
}
+void BtnGridView::show_hide_arrows() {
+ if (highlighted_item == 0) {
+ set_arrow_up_enabled(false);
+ } else {
+ set_arrow_up_enabled(true);
+ }
+ if (highlighted_item == (menu_items.size() - 1)) {
+ set_arrow_down_enabled(false);
+ } else {
+ set_arrow_down_enabled(true);
+ }
+}
+
void BtnGridView::update_items() {
size_t i = 0;
- Color bg_color = portapack::persistent_memory::menu_color();
- if ((menu_items.size()) > (displayed_max + offset)) {
- more = true;
- blink = true;
- } else
- more = false;
+ Color bg_color = portapack::persistent_memory::menu_color();
for (auto& item : menu_item_views) {
if ((i + offset) >= menu_items.size()) {
@@ -189,14 +246,23 @@ NewButton* BtnGridView::item_view(size_t index) const {
return menu_item_views[index].get();
}
+void BtnGridView::show_arrows_enabled(bool enabled) {
+ show_arrows = enabled;
+ if (!enabled) {
+ remove_child(&button_pgup);
+ remove_child(&button_pgdown);
+ }
+}
+
bool BtnGridView::set_highlighted(int32_t new_value) {
int32_t item_count = (int32_t)menu_items.size();
if (new_value < 0)
return false;
- if (new_value >= item_count)
+ if (new_value >= item_count) {
new_value = item_count - 1;
+ }
if (((uint32_t)new_value > offset) && ((new_value - offset) >= displayed_max)) {
// Shift BtnGridView up
@@ -222,6 +288,8 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
if (visible())
item_view(highlighted_item - offset)->focus();
+ show_hide_arrows();
+
return true;
}
@@ -235,21 +303,22 @@ void BtnGridView::on_focus() {
void BtnGridView::on_blur() {
#if 0
- if (!keep_highlight)
- item_view(highlighted_item - offset)->unhighlight();
+ if (!keep_highlight)
+ item_view(highlighted_item - offset)->unhighlight();
#endif
}
void BtnGridView::on_show() {
on_populate();
-
View::on_show();
+ set_highlighted(highlighted_item);
}
void BtnGridView::on_hide() {
View::on_hide();
-
clear();
+ set_arrow_up_enabled(false);
+ set_arrow_down_enabled(false);
}
bool BtnGridView::on_key(const KeyEvent key) {
diff --git a/firmware/application/ui/ui_btngrid.hpp b/firmware/application/ui/ui_btngrid.hpp
index e862b2c73..a10115991 100644
--- a/firmware/application/ui/ui_btngrid.hpp
+++ b/firmware/application/ui/ui_btngrid.hpp
@@ -55,7 +55,7 @@ void load_blacklist();
class BtnGridView : public View {
public:
- BtnGridView(Rect new_parent_rect = {0, 0, 240, 304}, bool keep_highlight = false);
+ BtnGridView(Rect new_parent_rect = {0, 0, screen_width, screen_height - 16}, bool keep_highlight = false);
~BtnGridView();
@@ -68,11 +68,18 @@ class BtnGridView : public View {
NewButton* item_view(size_t index) const;
+ bool show_arrows{true}; // flag used to hide arrows in main menu
+ void show_arrows_enabled(bool enabled);
+
bool set_highlighted(int32_t new_value);
uint32_t highlighted_index();
void set_parent_rect(const Rect new_parent_rect) override;
- void set_arrow_enabled(bool enabled);
+ bool arrow_up_enabled{false};
+ bool arrow_down_enabled{false};
+ void set_arrow_up_enabled(bool enabled);
+ void set_arrow_down_enabled(bool enabled);
+ void show_hide_arrows();
void on_focus() override;
void on_blur() override;
void on_show() override;
@@ -82,30 +89,33 @@ class BtnGridView : public View {
bool blacklisted_app(GridItem new_item);
void update_items();
+ void set_btn_min_max_height(uint8_t min, uint8_t max) {
+ btn_h_min = min;
+ btn_h_max = max;
+ }
protected:
virtual void on_populate() = 0;
private:
int rows_{3};
- void on_tick_second();
-
+ uint8_t btn_h_min{40};
+ uint8_t btn_h_max{60};
bool keep_highlight{false};
- SignalToken signal_token_tick_second{};
std::vector menu_items{};
std::vector> menu_item_views{};
- Image arrow_more{
- {228, 320 - 8, 8, 8},
- &bitmap_more,
- Theme::getInstance()->bg_darkest->foreground,
- Theme::getInstance()->bg_darkest->background};
+ Button button_pgup{
+ {0, 1324, 120, 16},
+ " "};
- int button_w = 240 / rows_;
- static constexpr int button_h = 48;
- bool blink = false;
- bool more = false;
+ Button button_pgdown{
+ {121, 1324, 119, 16},
+ " "};
+
+ int button_w = screen_width / rows_;
+ int button_h = 48;
size_t displayed_max{0};
size_t highlighted_item{0};
size_t offset{0};
diff --git a/firmware/application/ui/ui_freq_field.hpp b/firmware/application/ui/ui_freq_field.hpp
index c63e37917..d604cf1da 100644
--- a/firmware/application/ui/ui_freq_field.hpp
+++ b/firmware/application/ui/ui_freq_field.hpp
@@ -53,13 +53,21 @@ class BoundFrequencyField : public FrequencyField {
};
on_edit = [this, &nav]() {
+ if (on_edit_shown)
+ on_edit_shown();
auto freq_view = nav.push(model->target_frequency());
freq_view->on_changed = [this](rf::Frequency f) {
set_value(f);
};
+ nav.set_on_pop([this]() {
+ if (on_edit_hidden)
+ on_edit_hidden();
+ });
};
}
+ std::function on_edit_shown{}; // fired, when the FrequencyKeypadView pops up
+ std::function on_edit_hidden{}; // fired, when the FrequencyKeypadView ended
// TODO: override set_step and update the rx model then call base.
};
diff --git a/firmware/application/ui/ui_geomap.cpp b/firmware/application/ui/ui_geomap.cpp
index d2c895a64..83defae98 100644
--- a/firmware/application/ui/ui_geomap.cpp
+++ b/firmware/application/ui/ui_geomap.cpp
@@ -42,7 +42,7 @@ GeoPos::GeoPos(
const alt_unit altitude_unit,
const spd_unit speed_unit)
: altitude_unit_(altitude_unit), speed_unit_(speed_unit) {
- set_parent_rect({pos, {30 * 8, 3 * 16}});
+ set_parent_rect({pos, {screen_width, 3 * 16}});
add_children({&labels_position,
&label_spd_position,
@@ -110,6 +110,7 @@ GeoPos::GeoPos(
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
if (speed_unit_ == MPH) text_speed_unit.set("mph");
+ if (speed_unit_ == KNOTS) text_speed_unit.set("knots");
if (speed_unit_ == HIDDEN) {
text_speed_unit.hidden(true);
label_spd_position.hidden(true);
diff --git a/firmware/application/ui/ui_geomap.hpp b/firmware/application/ui/ui_geomap.hpp
index 772efedac..621816192 100644
--- a/firmware/application/ui/ui_geomap.hpp
+++ b/firmware/application/ui/ui_geomap.hpp
@@ -81,6 +81,7 @@ class GeoPos : public View {
NONE = 0,
MPH,
KMPH,
+ KNOTS,
HIDDEN = 255
};
@@ -134,7 +135,7 @@ class GeoPos : public View {
{12 * 8, 0 * 16, 2 * 8, 16},
""};
Text text_speed_unit{
- {25 * 8, 0 * 16, 4 * 8, 16},
+ {25 * 8, 0 * 16, 5 * 8, 16},
""};
NumberField field_lat_degrees{
diff --git a/firmware/application/ui/ui_receiver.hpp b/firmware/application/ui/ui_receiver.hpp
index 2b702dd92..ef7801b97 100644
--- a/firmware/application/ui/ui_receiver.hpp
+++ b/firmware/application/ui/ui_receiver.hpp
@@ -29,6 +29,7 @@
#include "irq_controls.hpp"
#include "rf_path.hpp"
+#include "freqman_db.hpp"
#include
#include
@@ -37,6 +38,10 @@
#define MAX_UFREQ 7200000000 // maximum usable frequency
+using option_db_t = std::pair;
+using options_db_t = std::vector;
+extern options_db_t freqman_steps;
+
namespace ui {
class FrequencyField : public Widget {
@@ -221,7 +226,7 @@ class FrequencyKeypadView : public View {
static constexpr int text_digits = mhz_digits + 1 + submhz_digits;
Text text_value{
- {0, 4, 240, 16}};
+ {0, 4, screen_width, 16}};
std::array