diff --git a/.github/workflows/create_nightly_release.yml b/.github/workflows/create_nightly_release.yml
index 0d2e88e3..4ed96367 100644
--- a/.github/workflows/create_nightly_release.yml
+++ b/.github/workflows/create_nightly_release.yml
@@ -54,19 +54,22 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
- mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
+ mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
+ - name: Prepare Firmware ZIP
+ run: |
+ cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
- name: Create Firmware ZIP
run: |
- zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
+ zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
- mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
+ mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/create_stable_release.yml b/.github/workflows/create_stable_release.yml
index ca99b66f..c6284380 100644
--- a/.github/workflows/create_stable_release.yml
+++ b/.github/workflows/create_stable_release.yml
@@ -38,19 +38,22 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
- mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
+ mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
+ - name: Prepare Firmware ZIP
+ run: |
+ cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
- name: Create Firmware ZIP
run: |
- zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
+ zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
- mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
+ mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/past_version.txt b/.github/workflows/past_version.txt
index 0ac852dd..f3b15f3f 100644
--- a/.github/workflows/past_version.txt
+++ b/.github/workflows/past_version.txt
@@ -1 +1 @@
-v2.0.1
+v2.0.2
diff --git a/.github/workflows/version.txt b/.github/workflows/version.txt
index f3b15f3f..1defe531 100644
--- a/.github/workflows/version.txt
+++ b/.github/workflows/version.txt
@@ -1 +1 @@
-v2.0.2
+v2.1.0
diff --git a/.gitmodules b/.gitmodules
index eaadf0d4..dd2d6545 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "hackrf"]
path = hackrf
- url = https://github.com/mossmann/hackrf.git
+ url = https://github.com/portapack-mayhem/hackrf.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a9b6cbb0..3aff31c8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,6 +23,12 @@ cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
+set(CMAKE_COLOR_MAKEFILE ON)
+
+add_compile_options(-fdiagnostics-color=always)
+
+option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
+
project(portapack-h1)
set(EXPECTED_GCC_VERSION "9.2.1")
@@ -59,10 +65,11 @@ message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
-add_subdirectory(hackrf-portapack)
+add_subdirectory(hackrf/firmware)
set(HACKRF_FIRMWARE_DFU_FILENAME hackrf_usb.dfu)
set(HACKRF_FIRMWARE_BIN_FILENAME hackrf_usb_ram.bin)
+set(HACKRF_FIRMWARE_FILENAME hackrf_usb.bin)
set(HACKRF_CPLD_XSVF_FILENAME default.xsvf)
set(HACKRF_PATH ${CMAKE_CURRENT_LIST_DIR}/hackrf)
@@ -72,13 +79,24 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
-# this seems causing some issues (ref. in discord discussions), so temporarily disabled, until figure out the pros and cons and bugs of this tool.
-#find_program(CCACHE "ccache")
-#if(CCACHE)
-# set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
-# set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
-# set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
-#endif(CCACHE)
+# this seems causing some issues (ref. in discord discussions), but it sometimes do helps, so you have your choice to use. it's default disabled, unless you use such command:
+# cmake -DUSE_CCACHE=ON ..
+# `make` or `make -j` or `make -j10` etc
+# the default behavior `cmake ..` isn't changed (aka don't use ccache)
+message(STATUS "-------v ccache info--------")
+if(USE_CCACHE)
+ find_program(CCACHE_FOUND ccache)
+ if(CCACHE_FOUND)
+ set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_FOUND})
+ set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_FOUND})
+ message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=OFF .. ` (cmake with Ninja) to turn off ccache")
+ else()
+ message(WARNING "ccache is enabled but not found on the system.")
+ endif()
+else()
+ message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=ON ..` (cmake with Ninja) to turn on ccache")
+endif()
+message(STATUS "-------^ ccache info--------")
enable_testing()
add_subdirectory(firmware)
diff --git a/README.md b/README.md
index fd9a2fef..367aa36e 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,23 @@
+
# PortaPack Mayhem
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](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/Hardware-overview) [](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
-
-*[PortaPack H2+HackRF+battery](https://grabify.link/7T28JP) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
+[](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)
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, check these:
-[](https://grabify.link/X4D5TF)
+[](https://share.hackrf.app/6HKX9A)
-[](https://grabify.link/9UZGEW) [](https://grabify.link/5MU2VH) [](https://grabify.link/C0J6ZR)
+[](https://share.hackrf.app/X4D5TF) [](https://share.hackrf.app/F9MPOO) [](https://share.hackrf.app/0JUHZ6) [](https://share.hackrf.app/C0J6ZR)
# Frequently Asked Questions
@@ -27,11 +25,12 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
-:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous [PortaPack H4M Mayhem](https://grabify.link/VPMPSL), featuring numerous improvements and accessories. Sold by our friends at OpenSourceSDRLab. Join their giveaways on discord (check the badge on top).
+
+:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) 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: A recommended one is this [PortaPack H2](https://grabify.link/7T28JP), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
+: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://grabify.link/HTDXG5), 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/LZPBH9), 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:
@@ -55,7 +54,7 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
## What if I really want something specific?
-If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
+If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?assignees=&labels=enhancement&projects=&template=02_feature_request.yml). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
## What if I need help?
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
diff --git a/dockerfile-nogit b/dockerfile-nogit
index d2f5f84d..916e510c 100644
--- a/dockerfile-nogit
+++ b/dockerfile-nogit
@@ -1,4 +1,4 @@
-FROM ubuntu:xenial
+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.
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update \
- && apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
+ && apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
-#Install current pip from PyPa
-RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
- python3 get-pip.py
-
-#Fetch additional dependencies from Python 3.x pip
-RUN pip install pyyaml \
- && ln -s /usr/bin/python3 /usr/bin/python \
- && ln -s /usr/bin/pip3 /usr/bin/pip
-
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
diff --git a/dockerfile-nogit-arm b/dockerfile-nogit-arm
index b5431ade..b4421c8d 100644
--- a/dockerfile-nogit-arm
+++ b/dockerfile-nogit-arm
@@ -1,4 +1,4 @@
-FROM ubuntu:xenial
+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.
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update \
- && apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
+ && apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
-#Install current pip from PyPa
-RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
- python3 get-pip.py
-
-#Fetch additional dependencies from Python 3.x pip
-RUN pip install pyyaml \
- && ln -s /usr/bin/python3 /usr/bin/python \
- && ln -s /usr/bin/pip3 /usr/bin/pip
-
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt
index 0097ec59..bc613db1 100644
--- a/firmware/CMakeLists.txt
+++ b/firmware/CMakeLists.txt
@@ -33,7 +33,7 @@ set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
set(LZ4 lz4)
-set(FIRMWARE_NAME portapack-h1_h2-mayhem)
+set(FIRMWARE_NAME portapack-mayhem-firmware)
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
@@ -64,7 +64,7 @@ add_custom_command(
add_custom_target(
firmware
- DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
+ DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} ${HACKRF_FIRMWARE_FILENAME}
)
if(${GCC_VERSION_MISMATCH})
diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt
index 7f0d846c..8b4ce16c 100644
--- a/firmware/application/CMakeLists.txt
+++ b/firmware/application/CMakeLists.txt
@@ -117,6 +117,7 @@ set(CSRC
usb_serial_descriptor.c
usb_serial_endpoints.c
usb_serial_device_to_host.c
+ i2c_device_to_host.c
${HACKRF_PATH}/firmware/common/usb.c
${HACKRF_PATH}/firmware/common/usb_queue.c
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
@@ -125,12 +126,14 @@ set(CSRC
${CHIBIOS}/os/various/chprintf.c
)
+#look for all i2cdev_ files
+file(GLOB I2CDEV_SOURCES ${COMMON}/i2cdev_*.cpp)
+
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
set(CPPSRC
main.cpp
shell.cpp
- ${COMMON}/acars_packet.cpp
${COMMON}/adsb.cpp
${COMMON}/adsb_frame.cpp
${COMMON}/ais_baseband.cpp
@@ -176,8 +179,8 @@ set(CPPSRC
${COMMON}/ui_language.cpp
${COMMON}/utility.cpp
${COMMON}/wm8731.cpp
- ${COMMON}/ads1110.cpp
- ${COMMON}/max17055.cpp
+ ${COMMON}/i2cdevmanager.cpp
+ ${I2CDEV_SOURCES}
${COMMON}/battery.cpp
${COMMON}/performance_counter.cpp
${COMMON}/bmpfile.cpp
@@ -189,7 +192,6 @@ set(CPPSRC
core_control.cpp
database.cpp
de_bruijn.cpp
- #emu_cc1101.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
@@ -242,9 +244,9 @@ set(CPPSRC
hw/si5351.cpp
hw/spi_pp.cpp
hw/touch_adc.cpp
+ ook_file.cpp
ui_baseband_stats_view.cpp
ui_navigation.cpp
- ui_playdead.cpp
ui_record_view.cpp
ui_sd_card_status_view.cpp
ui/ui_alphanum.cpp
@@ -266,56 +268,41 @@ set(CPPSRC
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
ui/ui_bmpview.cpp
- apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
- # apps/analog_tv_app.cpp
apps/ble_comm_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
- # apps/gps_sim_app.cpp
- # apps/lge_app.cpp
apps/pocsag_app.cpp
- # apps/replay_app.cpp
apps/soundboard_app.cpp
- # apps/tpms_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
- # apps/ui_adsb_tx.cpp #moved to ext
apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_battinfo.cpp
apps/ui_bht_tx.cpp
apps/ui_bmp_file_viewer.cpp
apps/ui_btle_rx.cpp
- # apps/ui_coasterp.cpp
apps/ui_debug.cpp
- apps/ui_debug_battery.cpp
+ apps/ui_debug_max17055.cpp
apps/ui_dfu_menu.cpp
apps/ui_encoders.cpp
+ apps/ui_external_module_view.cpp
apps/ui_fileman.cpp
apps/ui_flash_utility.cpp
apps/ui_freqman.cpp
- apps/ui_fsk_rx.cpp
apps/ui_iq_trim.cpp
- # apps/ui_jammer.cpp
- # apps/ui_keyfob.cpp
- # apps/ui_lcr.cpp
apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
- # apps/ui_morse.cpp
- # apps/ui_nrf_rx.cpp
- # apps/ui_nuoptix.cpp
apps/ui_playlist.cpp
apps/ui_pocsag_tx.cpp
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
- apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
@@ -323,16 +310,11 @@ set(CPPSRC
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
- # apps/ui_spectrum_painter_image.cpp
- # apps/ui_spectrum_painter_text.cpp
- # apps/ui_spectrum_painter.cpp
apps/ui_ss_viewer.cpp
- # apps/ui_sstvtx.cpp #moved to ext
apps/ui_standalone_view.cpp
apps/ui_subghzd.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
- apps/ui_tone_search.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
@@ -343,14 +325,8 @@ set(CPPSRC
protocols/bht.cpp
protocols/dcs.cpp
protocols/encoders.cpp
- # protocols/lcr.cpp
protocols/modems.cpp
protocols/rds.cpp
- # ui_handwrite.cpp
- # ui_loadmodule.cpp
- # ui_numbers.cpp
- # ui_replay_view.cpp
- # ui_script.cpp
ui_sd_card_debug.cpp
config_mode.cpp
${CPLD_20150901_DATA_CPP}
diff --git a/firmware/application/apps/capture_app.cpp b/firmware/application/apps/capture_app.cpp
index d5082bab..ca5c5477 100644
--- a/firmware/application/apps/capture_app.cpp
+++ b/firmware/application/apps/capture_app.cpp
@@ -87,9 +87,12 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
- // Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz
+ // 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)) {
- option_format.set_selected_index(1);
+ option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
+ }
+ if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
+ option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
}
previous_bandwidth = bandwidth;
diff --git a/firmware/application/apps/replay_app.cpp b/firmware/application/apps/replay_app.cpp
deleted file mode 100644
index 832b41c6..00000000
--- a/firmware/application/apps/replay_app.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 Furrtek
- * Copyleft (ↄ) 2022 NotPike
- *
- * 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 "replay_app.hpp"
-#include "string_format.hpp"
-
-#include "ui_fileman.hpp"
-#include "io_file.hpp"
-#include "io_convert.hpp"
-
-#include "baseband_api.hpp"
-#include "metadata_file.hpp"
-#include "portapack.hpp"
-#include "portapack_persistent_memory.hpp"
-#include "utility.hpp"
-
-using namespace portapack;
-namespace fs = std::filesystem;
-
-namespace ui {
-
-void ReplayAppView::set_ready() {
- ready_signal = true;
-}
-
-void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
- file_path = new_file_path;
- File::Size file_size{};
-
- { // Get the size of the data file.
- File data_file;
- auto error = data_file.open(file_path);
- if (error) {
- file_error();
- return;
- }
-
- file_size = data_file.size();
- }
-
- // Get original record frequency if available.
- auto metadata_path = get_metadata_path(file_path);
- auto metadata = read_metadata_file(metadata_path);
-
- if (metadata) {
- field_frequency.set_value(metadata->center_frequency);
- sample_rate = metadata->sample_rate;
- } else {
- // TODO: This is interesting because it implies that the
- // The capture will just be replayed at the freq set on the
- // FrequencyField. Is that an intentional behavior?
- sample_rate = 500000;
- }
-
- // UI Fixup.
- text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
- progressbar.set_max(file_size);
- text_filename.set(truncate(file_path.filename().string(), 12));
-
- uint8_t sample_size = capture_file_sample_size(current()->path);
- auto duration = ms_duration(file_size, sample_rate, sample_size);
- text_duration.set(to_string_time_ms(duration));
-
- button_play.focus();
-}
-
-void ReplayAppView::on_tx_progress(const uint32_t progress) {
- progressbar.set_value(progress);
-}
-
-void ReplayAppView::focus() {
- button_open.focus();
-}
-
-void ReplayAppView::file_error() {
- nav_.display_modal("Error", "File read error.");
-}
-
-bool ReplayAppView::is_active() const {
- return (bool)replay_thread;
-}
-
-void ReplayAppView::toggle() {
- if (is_active()) {
- stop(false);
- } else {
- start();
- }
-}
-
-void ReplayAppView::start() {
- stop(false);
-
- std::unique_ptr reader;
-
- auto p = std::make_unique();
- auto open_error = p->open(file_path);
- if (open_error.is_valid()) {
- file_error();
- return; // Fixes TX bug if there's a file error
- } else {
- reader = std::move(p);
- }
-
- if (reader) {
- button_play.set_bitmap(&bitmap_stop);
- baseband::set_sample_rate(sample_rate, OversampleRate::x8);
-
- replay_thread = std::make_unique(
- std::move(reader),
- read_size, buffer_count,
- &ready_signal,
- [](uint32_t return_code) {
- ReplayThreadDoneMessage message{return_code};
- EventDispatcher::send_message(message);
- });
- }
-
- transmitter_model.set_sampling_rate(sample_rate * toUType(OversampleRate::x8));
- transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
- transmitter_model.enable();
-
- if (portapack::persistent_memory::stealth_mode()) {
- DisplaySleepMessage message;
- EventDispatcher::send_message(message);
- }
-}
-
-void ReplayAppView::stop(const bool do_loop) {
- if (is_active())
- replay_thread.reset();
-
- if (do_loop && check_loop.value()) {
- start();
- } else {
- transmitter_model.disable();
- button_play.set_bitmap(&bitmap_play);
- }
-
- ready_signal = false;
-}
-
-void ReplayAppView::handle_replay_thread_done(const uint32_t return_code) {
- if (return_code == ReplayThread::END_OF_FILE) {
- stop(true);
- } else if (return_code == ReplayThread::READ_ERROR) {
- stop(false);
- file_error();
- }
-
- progressbar.set_value(0);
-}
-
-ReplayAppView::ReplayAppView(
- NavigationView& nav)
- : nav_(nav) {
- baseband::run_image(portapack::spi_flash::image_tag_replay);
-
- add_children({
- &button_open,
- &text_filename,
- &text_sample_rate,
- &text_duration,
- &progressbar,
- &field_frequency,
- &tx_view, // now it handles previous rfgain, rfamp.
- &check_loop,
- &button_play,
- &waterfall,
- });
-
- button_play.on_select = [this](ImageButton&) {
- this->toggle();
- };
-
- button_open.on_select = [this, &nav](Button&) {
- auto open_view = nav.push(".C*");
- open_view->on_changed = [this](fs::path new_file_path) {
- on_file_changed(new_file_path);
- };
- };
-}
-
-ReplayAppView::~ReplayAppView() {
- transmitter_model.disable();
- baseband::shutdown();
-}
-
-void ReplayAppView::on_hide() {
- stop(false);
- // TODO: Terrible kludge because widget system doesn't notify Waterfall that
- // it's being shown or hidden.
- waterfall.on_hide();
- View::on_hide();
-}
-
-void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
- View::set_parent_rect(new_parent_rect);
-
- const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
- waterfall.set_parent_rect(waterfall_rect);
-}
-
-} /* namespace ui */
diff --git a/firmware/application/apps/replay_app.hpp b/firmware/application/apps/replay_app.hpp
deleted file mode 100644
index 716fa6cd..00000000
--- a/firmware/application/apps/replay_app.hpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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 __REPLAY_APP_HPP__
-#define __REPLAY_APP_HPP__
-
-#include "app_settings.hpp"
-#include "radio_state.hpp"
-#include "ui_widget.hpp"
-#include "ui_navigation.hpp"
-#include "ui_receiver.hpp"
-#include "ui_freq_field.hpp"
-#include "replay_thread.hpp"
-#include "ui_spectrum.hpp"
-#include "ui_transmitter.hpp"
-
-#include
-#include
-
-namespace ui {
-
-class ReplayAppView : public View {
- public:
- ReplayAppView(NavigationView& nav);
- ~ReplayAppView();
-
- void on_hide() override;
- void set_parent_rect(const Rect new_parent_rect) override;
- void focus() override;
-
- std::string title() const override { return "Replay"; };
-
- private:
- NavigationView& nav_;
- TxRadioState radio_state_{};
- app_settings::SettingsManager settings_{
- "tx_replay", app_settings::Mode::TX};
-
- static constexpr ui::Dim header_height = 3 * 16;
-
- uint32_t sample_rate = 0;
- int32_t tx_gain{47};
- bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
- static constexpr uint32_t baseband_bandwidth = 2500000;
- const size_t read_size{16384};
- const size_t buffer_count{3};
-
- void on_file_changed(const std::filesystem::path& new_file_path);
- void on_tx_progress(const uint32_t progress);
-
- void toggle();
- void start();
- void stop(const bool do_loop);
- bool is_active() const;
- void set_ready();
- void handle_replay_thread_done(const uint32_t return_code);
- void file_error();
-
- std::filesystem::path file_path{};
- std::unique_ptr replay_thread{};
- bool ready_signal{false};
-
- Button button_open{
- {0 * 8, 0 * 16, 10 * 8, 2 * 16},
- "Open file"};
-
- Text text_filename{
- {11 * 8, 0 * 16, 12 * 8, 16},
- "-"};
- Text text_sample_rate{
- {24 * 8, 0 * 16, 6 * 8, 16},
- "-"};
-
- Text text_duration{
- {11 * 8, 1 * 16, 6 * 8, 16},
- "-"};
- ProgressBar progressbar{
- {18 * 8, 1 * 16, 12 * 8, 16}};
-
- TxFrequencyField field_frequency{
- {0 * 8, 2 * 16},
- nav_};
-
- TransmitterView2 tx_view{
- {11 * 8, 2 * 16},
- /*short_ui*/ true};
-
- Checkbox check_loop{
- {21 * 8, 2 * 16},
- 4,
- "Loop",
- true};
- ImageButton button_play{
- {28 * 8, 2 * 16, 2 * 8, 1 * 16},
- &bitmap_play,
- Theme::getInstance()->fg_green->foreground,
- Theme::getInstance()->fg_green->background};
-
- spectrum::WaterfallView waterfall{};
-
- MessageHandlerRegistration message_handler_replay_thread_error{
- Message::ID::ReplayThreadDone,
- [this](const Message* const p) {
- const auto message = *reinterpret_cast(p);
- this->handle_replay_thread_done(message.return_code);
- }};
-
- MessageHandlerRegistration message_handler_fifo_signal{
- Message::ID::RequestSignal,
- [this](const Message* const p) {
- const auto message = static_cast(p);
- if (message->signal == RequestSignalMessage::Signal::FillRequest) {
- this->set_ready();
- }
- }};
-
- MessageHandlerRegistration message_handler_tx_progress{
- Message::ID::TXProgress,
- [this](const Message* const p) {
- const auto message = *reinterpret_cast(p);
- this->on_tx_progress(message.progress);
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__REPLAY_APP_HPP__*/
diff --git a/firmware/application/apps/soundboard_app.cpp b/firmware/application/apps/soundboard_app.cpp
index 9a00b98c..b8af1d0f 100644
--- a/firmware/application/apps/soundboard_app.cpp
+++ b/firmware/application/apps/soundboard_app.cpp
@@ -35,6 +35,8 @@ using namespace portapack;
namespace ui {
+#define FILE_PER_PAGE 50
+
bool SoundBoardView::is_active() const {
return (bool)replay_thread;
}
@@ -178,9 +180,9 @@ void SoundBoardView::refresh_list() {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();
// sounds[c].path = u"WAV/" + entry.path().native();
- if (count >= (page - 1) * 100 && count < page * 100) {
+ if (count >= (page - 1) * FILE_PER_PAGE && count < page * FILE_PER_PAGE) {
file_list.push_back(entry.path());
- if (file_list.size() == 100) {
+ if (file_list.size() == FILE_PER_PAGE) {
page++;
break;
}
@@ -225,7 +227,7 @@ void SoundBoardView::refresh_list() {
menu_view.set_highlighted(0); // Refresh
}
- if (file_list.size() < 100) {
+ if (file_list.size() < FILE_PER_PAGE) {
page = 1;
}
}
diff --git a/firmware/application/apps/ui_about.cpp b/firmware/application/apps/ui_about.cpp
deleted file mode 100644
index 681d67ec..00000000
--- a/firmware/application/apps/ui_about.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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 "cpld_update.hpp"
-#include "portapack.hpp"
-#include "event_m0.hpp"
-
-#include "ui_about.hpp"
-
-#include "portapack_shared_memory.hpp"
-#include "portapack_persistent_memory.hpp"
-#include "lpc43xx_cpp.hpp"
-
-#include
-#include
-
-using namespace lpc43xx;
-using namespace portapack;
-
-namespace ui {
-
-// This is pretty much WaterfallView but in the opposite direction
-CreditsWidget::CreditsWidget(
- Rect parent_rect)
- : Widget{parent_rect} {
-}
-
-void CreditsWidget::paint(Painter&) {
-}
-
-void CreditsWidget::on_show() {
- clear();
-
- const auto screen_r = screen_rect();
- display.scroll_set_area(screen_r.top(), screen_r.bottom());
-}
-
-void CreditsWidget::on_hide() {
- display.scroll_disable();
-}
-
-void CreditsWidget::new_row(
- const std::array& pixel_row) {
- // Glitch be here (see comment in main.cpp)
- const auto draw_y = display.scroll(-1);
-
- display.draw_pixels(
- {{0, draw_y - 1}, {240, 1}},
- pixel_row);
-}
-
-void CreditsWidget::clear() {
- display.fill_rectangle(
- screen_rect(),
- Theme::getInstance()->bg_darkest->background);
-}
-
-void AboutView::update() {
- size_t i = 0;
- std::array pixel_row;
-
- slow_down++;
- if (slow_down % 3 < 2) return;
-
- if (!timer) {
- if (loop) {
- credits_index = 0;
- loop = false;
- }
-
- text = credits[credits_index].text;
- timer = credits[credits_index].delay;
- start_pos = credits[credits_index].start_pos;
-
- if (timer < 0) {
- timer = 240;
- loop = true;
- } else
- timer += 16;
-
- render_line = 0;
- credits_index++;
- } else
- timer--;
-
- if (render_line < 16) {
- for (const auto c : text) {
- const auto glyph = style().font.glyph(c);
-
- const size_t start = (glyph.size().width() / 8) * render_line;
- for (Dim c = 0; c < glyph.size().width(); c++) {
- const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
- pixel_row[start_pos + i + c] = pixel ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background;
- }
-
- const auto advance = glyph.advance();
- i += advance.x();
- }
- render_line++;
- }
-
- credits_display.new_row(pixel_row);
-}
-
-AboutView::AboutView(
- NavigationView& nav) {
- add_children({&credits_display,
- &button_ok});
-
- button_ok.on_select = [&nav](Button&) {
- nav.pop();
- };
-}
-
-void AboutView::focus() {
- button_ok.focus();
-}
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_about.hpp b/firmware/application/apps/ui_about.hpp
deleted file mode 100644
index e4f5e0c9..00000000
--- a/firmware/application/apps/ui_about.hpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_ABOUT_H__
-#define __UI_ABOUT_H__
-
-#include "ui_widget.hpp"
-#include "ui_navigation.hpp"
-
-#include
-
-namespace ui {
-
-class CreditsWidget : public Widget {
- public:
- CreditsWidget(Rect parent_rect);
-
- void on_show() override;
- void on_hide() override;
-
- void paint(Painter&) override;
-
- void new_row(const std::array& pixel_row);
-
- private:
- void clear();
-};
-
-class AboutView : public View {
- public:
- AboutView(NavigationView& nav);
-
- void focus() override;
-
- std::string title() const override { return "About"; };
-
- private:
- void update();
-
- uint8_t credits_index{0};
- uint8_t render_line{0};
- Coord start_pos{0};
- uint8_t slow_down{0};
- int32_t timer{0};
- bool loop{false};
-
- std::string text{};
-
- typedef struct credits_t {
- size_t start_pos;
- std::string text;
- int32_t delay;
- } credits_t;
-
- // TODO: Make this dinamically centered and parse \n as the delay value so it is easy to maintain
- const credits_t credits[26] = {
- // 012345678901234567890123456789
- {60, "PortaPack Mayhem", 0},
- {60, "PortaPack|HAVOC", 0},
- {11 * 8, "Gurus J. Boone", 0},
- {18 * 8, "M. Ossmann", 16},
- {11 * 8, "HAVOC Furrtek", 16},
- {7 * 8, "POCSAG rx T. Sailer", 0},
- {18 * 8, "E. Oenal", 16},
- {0 * 8, "Radiosonde infos F4GMU", 0},
- {18 * 8, "RS1729", 16},
- {4 * 8, "RDS waveform C. Jacquet", 16},
- {7 * 8, "Xy. infos cLx", 16},
- {2 * 8, "OOK scan trick Samy Kamkar", 16},
- {7 * 8, "World map NASA", 16},
- {0 * 8, "TouchTunes infos Notpike", 16},
- {4 * 8, "Subaru infos Tom", 0},
- {18 * 8, "Wimmenhove", 16},
- {1 * 8, "GPS,TV,BTLE,NRF Shao", 24},
- {6 * 8, "Thanks & donators", 16},
- {1 * 8, "Rainer Matla Keld Norman", 0},
- {1 * 8, " Giorgio C. DC1RDB", 0},
- {1 * 8, " Sigmounte Waax", 0},
- {1 * 8, " Windyoona Channels", 0},
- {1 * 8, " F4GEV Pyr3x", 0},
- {1 * 8, " HB3YOE", 24},
- {11 * 8, "MMXVIII", -1}};
-
- CreditsWidget credits_display{
- {0, 16, 240, 240}};
-
- Button button_ok{
- {72, 272, 96, 24},
- "OK"};
-
- MessageHandlerRegistration message_handler_update{
- Message::ID::DisplayFrameSync,
- [this](const Message* const) {
- this->update();
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__UI_ABOUT_H__*/
diff --git a/firmware/application/apps/ui_about_demo.cpp b/firmware/application/apps/ui_about_demo.cpp
deleted file mode 100644
index 853b0c4e..00000000
--- a/firmware/application/apps/ui_about_demo.cpp
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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 "demofont.hpp"
-#include "ymdata.hpp"
-
-#include "cpld_update.hpp"
-#include "portapack.hpp"
-#include "audio.hpp"
-#include "event_m0.hpp"
-
-#include "ui_about.hpp"
-#include "touch.hpp"
-#include "sine_table.hpp"
-
-#include "portapack_shared_memory.hpp"
-#include "portapack_persistent_memory.hpp"
-#include "lpc43xx_cpp.hpp"
-
-#include
-#include
-
-using namespace lpc43xx;
-using namespace portapack;
-
-namespace ui {
-
-void AboutView::on_show() {
- transmitter_model.set_target_frequency(1337000000); // TODO: Change
- transmitter_model.set_baseband_configuration({
- .mode = 0,
- .sampling_rate = 1536000,
- .decimation_factor = 1,
- });
- transmitter_model.set_rf_amp(true);
- transmitter_model.set_lna(40);
- transmitter_model.set_vga(40);
- transmitter_model.enable();
-
- baseband::set_audiotx_data(32, 50, false, 0);
-
- // audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
-}
-
-void AboutView::render_video() {
- uint8_t p, r, luma, chroma, cy;
- ui::Color cc;
- char ch;
- float s;
-
- // Send framebuffer to LCD. Gotta go fast !
- display.render_box({30, 112}, {180, 72}, framebuffer);
-
- // Clear framebuffer to black
- memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
-
- // Drum hit palette animation
- if (drum > 1) drum--;
-
- // Render copper bars from Y buffer
- for (p = 0; p < 72; p++) {
- luma = copperbuffer[p] & 0x0F; // 0 is transparent
- if (luma) {
- chroma = copperbuffer[p] >> 4;
- cc = ui::Color(std::min((coppercolor[chroma][0] / luma) * drum, 255), std::min((coppercolor[chroma][1] / luma) * drum, 255), std::min((coppercolor[chroma][2] / luma) * drum, 255));
- for (r = 0; r < 180; r++)
- framebuffer[(p * 180) + r] = cc;
- }
- }
-
- // Scroll in/out state machine
- if (anim_state == 0) {
- // Scroll in
- if (ofy < 8) {
- ofy++;
- anim_state = 0;
- } else {
- anim_state = 1;
- }
- if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
- ofx += 8;
- anim_state = 0;
- }
- } else if (anim_state == 1) {
- // Just wait
- if (credits_timer == (30 * 3)) {
- credits_timer = 0;
- anim_state = 2;
- } else {
- credits_timer++;
- }
- } else {
- // Scroll out
- if (credits[credits_index].change == true) {
- if (ofy > -24) {
- ofy--;
- anim_state = 2;
- } else {
- anim_state = 0;
- }
- } else {
- anim_state = 0;
- }
- if (ofx < 180) {
- ofx += 8;
- anim_state = 2;
- }
-
- // Switch to next text
- if (anim_state == 0) {
- if (credits_index == 9)
- credits_index = 0;
- else
- credits_index++;
- ofx = -(strlen(credits[credits_index].name) * 16) - 16;
- }
- }
-
- // Sine text ("role")
- p = 0;
- while ((ch = credits[credits_index].role[p])) {
- draw_demoglyph({(ui::Coord)(8 + (p * 16)), (ui::Coord)(ofy + (sine_table_f32[((p * 16) + (phase >> 5)) & 0xFF] * 8))}, ch, paletteA);
- p++;
- }
-
- // Scroll text (name)
- p = 0;
- while ((ch = credits[credits_index].name[p])) {
- draw_demoglyph({(ui::Coord)(ofx + (p * 16)), 56}, ch, paletteB);
- p++;
- }
-
- // Clear bars Y buffer
- memset(copperbuffer, 0, 72);
-
- // Render bars to Y buffer
- for (p = 0; p < 5; p++) {
- cy = copperbars[p];
- for (r = 0; r < 16; r++)
- copperbuffer[cy + r] = copperluma[r] + (p << 4);
- }
-
- // Animate bars positions
- for (p = 0; p < 5; p++) {
- s = sine_table_f32[((p * 32) + (phase / 24)) & 0xFF];
- s += sine_table_f32[((p * 16) + (phase / 35)) & 0xFF];
- copperbars[p] = 28 + (uint8_t)(s * 14);
- }
-
- phase += 128;
-}
-
-void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
- uint8_t x, y, c, cl, cr;
- uint16_t che;
- int16_t lbx, il;
-
- // Map ASCII to font bitmap
- if ((ch >= 32) && (ch < 96))
- che = char_map[ch - 32];
- else
- che = 0xFF;
-
- if (che < 0xFF) {
- che = (che * 128) + 48; // Start in bitmap
-
- il = (180 * p.y) + p.x; // Start il framebuffer
-
- for (y = 0; y < 16; y++) {
- if (p.y + y >= 72) break; // Over bottom of framebuffer, abort
- if (p.y + y >= 0) {
- for (x = 0; x < 8; x++) {
- c = demofont_bin[x + (y * 8) + che]; // Split byte in 2 4BPP pixels
- cl = c >> 4;
- cr = c & 0x0F;
- lbx = p.x + (x * 2);
- if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
- lbx++;
- il++;
- if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
- il++;
- }
- il += 180 - 16;
- } else {
- il += 180;
- }
- }
- }
-}
-
-void AboutView::render_audio() {
- uint8_t i, ymdata;
- uint16_t ym_render_cnt;
-
- // This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet
-
- // Render 1024 music samples
- for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
- // Update registers at 48000/960 = 50Hz
- if (ym_sample_cnt == 0) {
- // "Decompress" on the fly and update YM registers
- for (i = 0; i < 14; i++) {
- if (!ym_regs[i].cnt) {
- // New run
- ymdata = ymdata_bin[ym_regs[i].ptr++];
- ym_regs[i].cnt = ymdata & 0x7F;
- if (ymdata & 0x80) {
- ym_regs[i].same = true;
- ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
- } else {
- ym_regs[i].same = false;
- }
- // Detect drum on channel B
- if (i == 3)
- if (ym_regs[3].value > 2) drum = 4;
- }
- if (ym_regs[i].same == false) {
- ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
- if (i == 13) {
- // Update envelope attributes
- ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
- if (!(ym_regs[13].value & 8)) {
- ym_env_hold = 1;
- ym_env_alt = ym_env_att;
- } else {
- ym_env_hold = ym_regs[13].value & 1;
- ym_env_alt = ym_regs[13].value & 2;
- }
- // Reset envelope counter
- ym_env_step = 0x1F;
- ym_env_holding = 0;
- ym_env_vol = (ym_env_step ^ ym_env_att);
- }
- }
- ym_regs[i].cnt--;
- }
- ym_frame++;
- }
-
- // Square wave oscillators
- // 2457600/16/48000 = 3.2, but 4 sounds better than 3...
- for (i = 0; i < 3; i++) {
- ym_osc_cnt[i] += 4;
- if (ym_osc_cnt[i] >= (ym_regs[i * 2].value | ((ym_regs[(i * 2) + 1].value & 0x0f) << 8))) {
- ym_osc_cnt[i] = 0;
- ym_osc_out[i] ^= 1;
- }
- }
-
- // Noise generator
- ym_noise_cnt += 4;
- if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
- ym_noise_cnt = 0;
- ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
- ym_rng >>= 1;
- }
-
- // Mix tones and noise
- for (i = 0; i < 3; i++)
- ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));
-
- // Envelope generator
- if (!ym_env_holding) {
- ym_env_cnt += 8;
- if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value << 8))) {
- ym_env_cnt = 0;
- ym_env_step--;
- if (ym_env_step < 0) {
- if (ym_env_hold) {
- if (ym_env_alt)
- ym_env_att ^= 0x1F;
- ym_env_holding = 1;
- ym_env_step = 0;
- } else {
- if (ym_env_alt && (ym_env_step & 0x20))
- ym_env_att ^= 0x1F;
- ym_env_step &= 0x1F;
- }
- }
- }
- }
- ym_env_vol = (ym_env_step ^ ym_env_att);
-
- ym_out = 0;
- for (i = 0; i < 3; i++) {
- if (ym_regs[i + 8].value & 0x10) {
- // Envelope mode
- ym_out += (ym_ch[i] ? ym_env_vol : 0);
- } else {
- // Fixed mode
- ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
- }
- }
-
- ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;
-
- if (ym_sample_cnt < 960) {
- ym_sample_cnt++;
- } else {
- ym_sample_cnt = 0;
- }
-
- // Loop
- if (ym_frame == ym_frames) ym_init();
- }
-}
-
-void AboutView::update() {
- if (framebuffer) {
- // Update 1 out of 2 frames, 60Hz is very laggy
- if (refresh_cnt & 1) render_video();
- refresh_cnt++;
- }
-
- // Slowly increase volume to avoid jumpscare
- if (headphone_vol < (70 << 2)) {
- audio::headphone::set_volume(volume_t::decibel((headphone_vol / 4) - 99) + audio::headphone::volume_range().max);
- headphone_vol++;
- }
-}
-
-void AboutView::ym_init() {
- uint8_t reg;
-
- for (reg = 0; reg < 14; reg++) {
- ym_regs[reg].cnt = 0;
- // Pick up start pointers for each YM registers RLE blocks
- ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg * 2) + 3]) << 8) + ymdata_bin[(reg * 2) + 2];
- ym_regs[reg].same = false; // Useless ?
- ym_regs[reg].value = 0; // Useless ?
- }
-
- ym_frame = 0;
-}
-
-AboutView::AboutView(
- NavigationView& nav) {
- uint8_t p, c;
-
- baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
-
- add_children({{
- &text_title,
- &text_firmware,
- &text_cpld_hackrf,
- &text_cpld_hackrf_status,
- &button_ok,
- }});
-
- if (cpld_hackrf_verify_eeprom()) {
- text_cpld_hackrf_status.set(" OK");
- } else {
- text_cpld_hackrf_status.set("BAD");
- }
-
- // Politely ask for about 26kB
- framebuffer = (ui::Color*)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));
-
- if (framebuffer) {
- memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
-
- // Copy original font palette
- c = 0;
- for (p = 0; p < 48; p += 3)
- paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p + 1], demofont_bin[p + 2]);
-
- // Increase red in another one
- c = 0;
- for (p = 0; p < 48; p += 3)
- paletteB[c++] = ui::Color(std::min(demofont_bin[p] + 64, 255), demofont_bin[p + 1], demofont_bin[p + 2]);
- }
-
- // Init YM synth
- ym_frames = ((uint16_t)(ymdata_bin[1]) << 8) + ymdata_bin[0];
- ym_init();
-
- button_ok.on_select = [this, &nav](Button&) {
- if (framebuffer) chHeapFree(framebuffer); // Do NOT forget this
- nav.pop();
- };
-}
-
-AboutView::~AboutView() {
- transmitter_model.disable();
- baseband::shutdown();
-}
-
-void AboutView::focus() {
- button_ok.focus();
-}
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_about_demo.hpp b/firmware/application/apps/ui_about_demo.hpp
deleted file mode 100644
index 37342c53..00000000
--- a/firmware/application/apps/ui_about_demo.hpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_ABOUT_H__
-#define __UI_ABOUT_H__
-
-#include "ui_widget.hpp"
-#include "ui_menu.hpp"
-#include "ui_navigation.hpp"
-#include "transmitter_model.hpp"
-#include "baseband_api.hpp"
-
-#include
-
-namespace ui {
-
-class AboutView : public View {
- public:
- AboutView(NavigationView& nav);
- ~AboutView();
-
- void on_show() override;
- void focus() override;
-
- private:
- void ym_init();
- void update();
- void render_video();
- void render_audio();
- void draw_demoglyph(ui::Point p, char ch, ui::Color* pal);
- uint16_t debug_cnt = 0;
-
- typedef struct ymreg_t {
- uint8_t value;
- uint8_t cnt;
- uint16_t ptr;
- bool same;
- } ymreg_t;
-
- uint16_t headphone_vol = 5 << 2;
-
- ymreg_t ym_regs[14];
- uint16_t ym_frames;
- uint16_t ym_frame;
- uint8_t drum = 0;
- uint16_t ym_osc_cnt[3];
- uint32_t ym_rng = 1;
- uint16_t ym_noise_cnt;
- uint8_t ym_env_att, ym_env_hold, ym_env_alt, ym_env_holding, ym_env_vol;
- int8_t ym_env_step;
- uint16_t ym_env_cnt;
- uint8_t ym_osc_out[3];
- uint8_t ym_ch[3];
- uint8_t ym_out;
- uint16_t ym_sample_cnt = 0;
-
- int8_t ym_buffer[1024];
-
- uint8_t refresh_cnt;
- ui::Color paletteA[16];
- ui::Color paletteB[16];
- ui::Color* framebuffer;
- uint32_t phase = 0;
- uint8_t copperbars[5] = {0};
- uint8_t copperbuffer[72] = {0};
-
- uint8_t anim_state = 0;
- uint8_t credits_index = 0;
- uint16_t credits_timer = 0;
-
- int16_t ofx = -180, ofy = -24;
-
- const uint8_t char_map[64] = {0xFF, 27, 46, 0xFF, 0xFF, 0xFF, 28, 45,
- 58, 59, 0xFF, 43, 40, 57, 26, 42,
- 29, 30, 31, 32, 33, 34, 35, 36,
- 37, 38, 41, 0xFF, 0xFF, 0xFF, 0xFF, 44,
- 0xFF, 0, 1, 2, 3, 4, 5, 6,
- 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-
- const uint8_t copperluma[16] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8};
- const uint8_t coppercolor[5][3] = {{255, 0, 0},
- {0, 255, 0},
- {0, 0, 255},
- {255, 0, 255},
- {255, 255, 0}};
-
- typedef struct credits_t {
- char role[12];
- char name[12];
- bool change;
- } credits_t;
-
- // 0123456789A 0123456789A
- const credits_t credits[10] = {{"GURUS", "J. BOONE", false},
- {"GURUS", "M. OSSMANN", true},
- {"BUGS", "FURRTEK", true},
- {"RDS WAVE", "C. JACQUET", true},
- {"POCSAG RX", "T. SAILER", false},
- {"POCSAG RX", "E. OENAL", true},
- {"XYLOS DATA", "CLX", true},
- {"GREETS TO", "SIGMOUNTE", false},
- {"GREETS TO", "WINDYOONA", true},
- {"THIS MUSIC", "BIG ALEC", true}};
-
- Text text_title{
- {100, 32, 40, 16},
- "About",
- };
-
- Text text_firmware{
- {0, 236, 240, 16},
- "Version " VERSION_STRING,
- };
-
- Text text_cpld_hackrf{
- {0, 252, 11 * 8, 16},
- "HackRF CPLD",
- };
-
- Text text_cpld_hackrf_status{
- {240 - 3 * 8, 252, 3 * 8, 16},
- "???"};
-
- Button button_ok{
- {72, 272, 96, 24},
- "OK"};
-
- MessageHandlerRegistration message_handler_update{
- Message::ID::DisplayFrameSync,
- [this](const Message* const) {
- this->update();
- }};
-
- MessageHandlerRegistration message_handler_fifo_signal{
- Message::ID::FIFOSignal,
- [this](const Message* const p) {
- const auto message = static_cast(p);
- if (message->signaltype == 1) {
- this->render_audio();
- baseband::set_fifo_data(ym_buffer);
- }
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__UI_ABOUT_H__*/
diff --git a/firmware/application/apps/ui_about_simple.cpp b/firmware/application/apps/ui_about_simple.cpp
index 153d4d18..1e78e5e9 100644
--- a/firmware/application/apps/ui_about_simple.cpp
+++ b/firmware/application/apps/ui_about_simple.cpp
@@ -1,47 +1,60 @@
#include "ui_about_simple.hpp"
+#include
+
+#define ROLL_SPEED_FRAME_PER_LINE 60
+// cuz frame rate of pp screen is probably 60, scroll per sec
namespace ui {
-// TODO: Generate this automatically from github
// Information: a line starting with a '#' will be yellow coloured
-const std::string authors_list[] = {
+constexpr std::string_view authors_list[] = {
"# * List of contributors * ",
" ",
- "#Mayhem:",
- "eried,euquiq,gregoryfenton",
- "johnelder,jwetzell,nnemanjan00",
- "N0vaPixel,klockee,gullradriel",
- "jamesshao8,ITAxReal,rascafr",
- "mcules,dqs105,strijar",
- "zhang00963,RedFox-Fr,aldude999",
- "East2West,fossum,ArjanOnwezen",
- "vXxOinvizioNxX,teixeluis",
- "Brumi-2021,texasyojimbo",
- "heurist1,intoxsick,ckuethe",
- "notpike,jLynx,zigad",
- "MichalLeonBorsuk,jimilinuxguy",
- "kallanreed,bernd-herzog",
- "NotherNgineer,zxkmm,u-foka",
- "Netro,HTotoo",
+ "#Mayhem-Firmware:",
+ "jboone,eried,furrtek,",
+ "NotherNgineer,gullradriel,",
+ "jLynx,kallanreed,Brumi-2021,",
+ "htotoo,bernd-herzog,zxkmm,",
+ "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,",
+ "RocketGod-git,mrmookie,",
+ "ITAxReal,F33RNI,F4GEV,",
+ "rusty-labs,mjwaxios,andrej-mk,",
+ "RedFox-Fr,nemanjan00,",
+ "MichalLeonBorsuk,",
+ "MatiasFernandez,Giorgiofox,",
+ "ckuethe",
" ",
"#Havoc:",
- "furrtek,mrmookie,NotPike",
- "mjwaxios,ImDroided,Giorgiofox",
- "F4GEV,z4ziggy,xmycroftx",
- "troussos,silascutler",
- "nickbouwhuis,msoose,leres",
- "joakar,dhoetger,clem-42",
- "brianlechthaler,ZeroChaos-...",
+ "jboone,furrtek,eried,argilo,",
+ "mrmookie,Giorgiofox,ImDroided,",
+ "mjwaxios,F4GEV,OpCode1300,",
+ "ZeroChaos-,RndmNmbr,",
+ "silascutler,troussos,z4ziggy,",
+ "clem-42,dhoetger,NickBouwhuis,",
+ "xmycroftx,Maescool,KimIV,",
+ "joakar,leres,brianlechthaler,",
+ "N0vaPixel",
" ",
"#PortaPack:",
- "jboone,argilo",
+ "jboone,mossmann,martinling,",
+ "argilo,eried,ZeroChaos-,",
+ "RndmNmbr",
" ",
"#HackRF:",
- "mossmann,dominicgs,bvernoux",
- "bgamari,schneider42,miek",
- "willcode,hessu,Sec42",
- "yhetti,ckuethe,smunaut",
- "wishi,mrbubble62,scateu..."};
+ "mossmann,jboone,dominicgs,",
+ "martinling,bvernoux,miek,",
+ "bgamari,schneider42,straithe,",
+ "grvvy,willcode,hessu,yhetti,",
+ "Sec42,ckuethe",
+ " "};
AboutView::AboutView(NavigationView& nav) {
add_children({&menu_view,
@@ -59,18 +72,18 @@ AboutView::AboutView(NavigationView& nav) {
button_ok.focus();
};
- for (const std::string& authors_line : authors_list) {
+ for (auto& authors_line : authors_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] == '#') {
menu_view.add_item(
- {authors_line.substr(1, authors_line.size() - 1),
+ {(std::string)authors_line.substr(1, authors_line.size() - 1),
ui::Theme::getInstance()->fg_yellow->foreground,
nullptr,
nullptr});
} else {
menu_view.add_item(
- {authors_line,
+ {(std::string)authors_line,
Theme::getInstance()->bg_darkest->foreground,
nullptr,
nullptr});
@@ -79,10 +92,42 @@ AboutView::AboutView(NavigationView& nav) {
}
}
+void AboutView::on_frame_sync() {
+ if (interacted) return;
+
+ if (frame_sync_count++ % ROLL_SPEED_FRAME_PER_LINE == 0) {
+ const auto current = menu_view.highlighted_index();
+ const auto count = menu_view.item_count();
+
+ if (current < count - 1) {
+ 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
+}
+
+bool AboutView::on_touch(const TouchEvent) {
+ interacted = true;
+ return false;
+}
+
+bool AboutView::on_key(const KeyEvent) {
+ interacted = true;
+ return false;
+}
+
+bool AboutView::on_encoder(const EncoderEvent) {
+ interacted = true;
menu_view.focus();
- // put focus on last text line to make it more obvious that list is scrollable
- menu_view.set_highlighted(10);
+ return false;
}
} /* namespace ui */
diff --git a/firmware/application/apps/ui_about_simple.hpp b/firmware/application/apps/ui_about_simple.hpp
index f5e8488f..637a0312 100644
--- a/firmware/application/apps/ui_about_simple.hpp
+++ b/firmware/application/apps/ui_about_simple.hpp
@@ -14,6 +14,13 @@ class AboutView : public View {
std::string title() const override { return "About"; };
private:
+ virtual bool on_key(const KeyEvent event);
+ virtual bool on_encoder(const EncoderEvent event);
+ virtual bool on_touch(const TouchEvent event);
+
+ bool interacted{false};
+ uint16_t frame_sync_count{0};
+ void on_frame_sync();
MenuView menu_view{
{0, 0, 240, 264},
true};
@@ -22,6 +29,12 @@ class AboutView : public View {
{240 / 3, 270, 240 / 3, 24},
"OK",
};
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->on_frame_sync();
+ }};
};
} // namespace ui
diff --git a/firmware/application/apps/ui_aprs_rx.cpp b/firmware/application/apps/ui_aprs_rx.cpp
index 9296a67d..d5ff4f23 100644
--- a/firmware/application/apps/ui_aprs_rx.cpp
+++ b/firmware/application/apps/ui_aprs_rx.cpp
@@ -94,21 +94,39 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
record_view.set_sampling_rate(24000);
options_region.on_change = [this](size_t, int32_t i) {
- if (i == 0) {
+ field_frequency.on_change = [this](rf::Frequency f) { (void)f; };
+ if (i == 0) { // MAN Manual setting
+ 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 == 1) {
+ } else if (i == 2) { // EUR
field_frequency.set_value(144800000);
- } else if (i == 2) {
+ } else if (i == 3) { // AUS
field_frequency.set_value(145175000);
- } else if (i == 3) {
+ } else if (i == 4) { // NZ
field_frequency.set_value(144575000);
- } else if (i == 4) {
+ } else if (i == 5) { // ISS
field_frequency.set_value(145825000);
}
+ options_region_id = i;
+ receiver_model.set_target_frequency(field_frequency.value()); // Retune
+ field_frequency.on_change = [this](rf::Frequency f) {
+ aprs_rx_freq = f;
+ options_region.set_selected_index(0, true);
+ };
};
field_frequency.set_step(100);
- options_region.set_selected_index(0, true);
+ field_frequency.on_edit = [this]() {
+ auto freq_view = nav_.push(field_frequency.value());
+ freq_view->on_changed = [this](rf::Frequency f) {
+ aprs_rx_freq = f;
+ field_frequency.set_value(f);
+ };
+ };
+
+ // Note: setting the region is also going to sef field_frequency.on_change
+ options_region.set_selected_index(options_region_id, true);
logger = std::make_unique();
if (logger)
@@ -124,6 +142,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
void APRSRxView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
+ aprs_rx_freq = freq;
+ options_region.set_selected_index(0, true);
}
void APRSRxView::on_packet(const APRSPacketMessage* message) {
diff --git a/firmware/application/apps/ui_aprs_rx.hpp b/firmware/application/apps/ui_aprs_rx.hpp
index d2ebf6f5..6761842d 100644
--- a/firmware/application/apps/ui_aprs_rx.hpp
+++ b/firmware/application/apps/ui_aprs_rx.hpp
@@ -190,6 +190,8 @@ class APRSRxView : public View {
private:
void on_data(uint32_t value, bool is_data);
bool reset_console = false;
+ uint8_t options_region_id = 1; // default to North America
+ rf::Frequency aprs_rx_freq{144390000}; // default to North America frequency
NavigationView& nav_;
RxRadioState radio_state_{
@@ -198,7 +200,10 @@ class APRSRxView : public View {
3072000 /* sampling rate */
};
app_settings::SettingsManager settings_{
- "rx_aprs", app_settings::Mode::RX};
+ "rx_aprs",
+ app_settings::Mode::RX,
+ {{"options_region_id"sv, &options_region_id},
+ {"aprs_rx_freq"sv, &aprs_rx_freq}}};
uint8_t console_color{0};
std::string str_log{""};
@@ -220,15 +225,15 @@ class APRSRxView : public View {
OptionsField options_region{
{0 * 8, 0 * 8},
3,
- {{"NA ", 0},
- {"EUR", 1},
- {"AUS", 2},
- {"NZ ", 3},
- {"ISS", 4}}};
+ {{"MAN", 0},
+ {"NA ", 1},
+ {"EUR", 2},
+ {"AUS", 3},
+ {"NZ ", 4},
+ {"ISS", 5}}};
- RxFrequencyField field_frequency{
- {3 * 8, 0 * 16},
- nav_};
+ FrequencyField field_frequency{
+ {3 * 8, 0 * 16}};
// DEBUG
RecordView record_view{
diff --git a/firmware/application/apps/ui_battinfo.cpp b/firmware/application/apps/ui_battinfo.cpp
index fa875390..bb35fc22 100644
--- a/firmware/application/apps/ui_battinfo.cpp
+++ b/firmware/application/apps/ui_battinfo.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
+ * Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
@@ -49,10 +50,10 @@ void BattinfoView::update_result() {
text_voltage.set("UNKNOWN");
text_current.set("-");
text_charge.set("-");
- text_cycles.set("-");
+ // text_cycles.set("-");
text_ttef.set("-");
text_method.set("-");
- text_warn.set("");
+ // text_warn.set("");
return;
}
bool uichg = false;
@@ -74,7 +75,19 @@ void BattinfoView::update_result() {
text_current.hidden(false);
text_charge.hidden(false);
text_current.set(to_string_dec_int(current) + " mA");
- text_charge.set(current >= 0 ? "Charging" : "Discharging");
+ if (current >= 25) // when >25mA it is charging in any scenario
+ text_charge.set("Charging");
+ else {
+ if (current > -25) { // between -25 and 25
+ if (voltage > 4060 || percent == 100) { // the voltage is high enough, so it is full, that's why no mA
+ text_charge.set("Full");
+ } else {
+ text_charge.set("Holding"); // not enough voltage, so should charge. maybe the batttery is off, or USB power is not enough
+ }
+ } else {
+ text_charge.set("Discharging"); // less then -25mA, so it is discharging
+ }
+ }
labels_opt.hidden(false);
text_ttef.hidden(false);
@@ -83,26 +96,26 @@ void BattinfoView::update_result() {
labels_opt.hidden(true);
text_current.hidden(true);
text_charge.hidden(true);
- text_cycles.hidden(true);
+ // text_cycles.hidden(true);
text_ttef.hidden(true);
- text_warn.set("");
+ // text_warn.set("");
}
- if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
- text_cycles.hidden(false);
- uint16_t cycles = battery::BatteryManagement::get_cycles();
+ /* if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
+ // text_cycles.hidden(false);
+ uint16_t cycles = 0; // battery::BatteryManagement::get_cycles();
if (cycles < 2)
- text_warn.set("SoC improves after 2 cycles");
+ text_warn.set("SoC improves after each cycles");
else
text_warn.set("");
- text_cycles.set(to_string_dec_uint(cycles));
+ // text_cycles.set(to_string_dec_uint(cycles));
} else {
- text_cycles.hidden(true);
+ // text_cycles.hidden(true);
text_warn.set("");
- }
+ } */
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
text_ttef.hidden(false);
float ttef = 0;
- if (current <= 0) {
+ if (current <= 0) { // we keep this yet
ttef = battery::BatteryManagement::get_tte();
} else {
ttef = battery::BatteryManagement::get_ttf();
@@ -132,7 +145,7 @@ void BattinfoView::update_result() {
}
if (uichg) set_dirty();
// to update status bar too, send message in behalf of batt manager
- BatteryStateMessage msg{valid_mask, percent, current >= 0, voltage};
+ BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
EventDispatcher::send_message(msg);
}
@@ -147,8 +160,8 @@ BattinfoView::BattinfoView(NavigationView& nav)
&text_method,
&button_mode,
&button_exit,
- &text_cycles,
- &text_warn,
+ // &text_cycles,
+ // &text_warn,
&text_ttef});
button_exit.on_select = [this, &nav](Button&) {
@@ -185,4 +198,4 @@ BattinfoView::~BattinfoView() {
thread = nullptr;
}
}
-} // namespace ui
+} // namespace ui
\ No newline at end of file
diff --git a/firmware/application/apps/ui_battinfo.hpp b/firmware/application/apps/ui_battinfo.hpp
index 83b6112c..45960c2e 100644
--- a/firmware/application/apps/ui_battinfo.hpp
+++ b/firmware/application/apps/ui_battinfo.hpp
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
+ * Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
@@ -27,6 +28,7 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "string_format.hpp"
+#include "i2cdevmanager.hpp"
namespace ui {
class BattinfoView : public View {
@@ -61,7 +63,7 @@ class BattinfoView : public View {
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
- {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
+ // {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
};
@@ -83,13 +85,13 @@ class BattinfoView : public View {
Text text_ttef{
{13 * 8, 6 * 16, 10 * 16, 16},
"-"};
- Text text_cycles{
+ /* Text text_cycles{
{13 * 8, 7 * 16, 10 * 16, 16},
"-"};
Text text_warn{
- {2 * 8, 8 * 16, 30 * 8, 2 * 16},
- ""};
+ {1 * 8, 8 * 16, 30 * 8, 2 * 16},
+ ""}; */
Button button_mode{
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
@@ -104,4 +106,4 @@ class BattinfoView : public View {
} /* namespace ui */
-#endif /*__UI_BATTINFO__*/
+#endif /*__UI_BATTINFO__*/
\ No newline at end of file
diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp
index 54844d6f..daa986f4 100644
--- a/firmware/application/apps/ui_debug.cpp
+++ b/firmware/application/apps/ui_debug.cpp
@@ -36,7 +36,8 @@
#include "ui_font_fixed_8x16.hpp"
#include "ui_painter.hpp"
#include "ui_external_items_menu_loader.hpp"
-#include "ui_debug_battery.hpp"
+#include "ui_debug_max17055.hpp"
+#include "ui_external_module_view.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
@@ -73,91 +74,6 @@ void DebugMemoryView::focus() {
button_done.focus();
}
-/* TemperatureWidget *****************************************************/
-
-void TemperatureWidget::paint(Painter& painter) {
- const auto logger = portapack::temperature_logger;
-
- const auto rect = screen_rect();
- const Color color_background{0, 0, 64};
- const Color color_foreground = Theme::getInstance()->fg_green->foreground;
- const Color color_reticle{128, 128, 128};
-
- const auto graph_width = static_cast(logger.capacity()) * bar_width;
- const Rect graph_rect{
- rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
- graph_width, rect.height()};
- const Rect frame_rect{
- graph_rect.left() - 1, graph_rect.top() - 1,
- graph_rect.width() + 2, graph_rect.height() + 2};
- painter.draw_rectangle(frame_rect, color_reticle);
- painter.fill_rectangle(graph_rect, color_background);
-
- const auto history = logger.history();
- for (size_t i = 0; i < history.size(); i++) {
- const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
- const auto sample = history[i];
- const auto temp = temperature(sample);
- const auto y = screen_y(temp, graph_rect);
- const Dim bar_height = graph_rect.bottom() - y;
- painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
- }
-
- if (!history.empty()) {
- const auto sample = history.back();
- const auto temp = temperature(sample);
- const auto last_y = screen_y(temp, graph_rect);
- const Coord x = graph_rect.right() + 8;
- const Coord y = last_y - 8;
-
- painter.draw_string({x, y}, style(), temperature_str(temp));
- }
-
- const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
- for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
- const int32_t tick_length = 6;
- const auto tick_x = graph_rect.left() - tick_length;
- const auto tick_y = screen_y(temp, graph_rect);
- painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
- const auto text_x = graph_rect.left() - temp_len * 8 - 8;
- const auto text_y = tick_y - 8;
- painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
- }
-}
-
-TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
- // Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
- return sensor_value;
-}
-
-std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
- return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
-}
-
-Coord TemperatureWidget::screen_y(
- const temperature_t temperature,
- const Rect& rect) const {
- int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
- const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
- return y_limit;
-}
-
-/* TemperatureView *******************************************************/
-
-TemperatureView::TemperatureView(NavigationView& nav) {
- add_children({
- &text_title,
- &temperature_widget,
- &button_done,
- });
-
- button_done.on_select = [&nav](Button&) { nav.pop(); };
-}
-
-void TemperatureView::focus() {
- button_done.focus();
-}
-
/* RegistersWidget *******************************************************/
RegistersWidget::RegistersWidget(
@@ -226,8 +142,10 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
return radio::debug::second_if::register_read(register_number);
case CT_SI5351:
return portapack::clock_generator.read_register(register_number);
- case CT_BATTERY:
- return battery::BatteryManagement::read_register(register_number);
+ case CT_MAX17055: {
+ i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
+ return dev->read_register(register_number);
+ }
case CT_AUDIO:
return audio::debug::reg_read(register_number);
}
@@ -249,9 +167,11 @@ void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t v
case CT_SI5351:
portapack::clock_generator.write_register(register_number, value);
break;
- case CT_BATTERY:
- battery::BatteryManagement::write_register(register_number, value);
+ case CT_MAX17055: {
+ i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
+ dev->write_register(register_number, value);
break;
+ }
case CT_AUDIO:
audio::debug::reg_write(register_number, value);
break;
@@ -465,9 +385,9 @@ void DebugPeripheralsMenuView::on_populate() {
{si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
{audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
});
- if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
+ if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
add_item(
- {"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push("MAX17055", RegistersWidgetConfig{CT_BATTERY, 256, 16, 16}); }});
+ {"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push("MAX17055", RegistersWidgetConfig{CT_MAX17055, 256, 16, 16}); }});
}
set_max_rows(2); // allow wider buttons
}
@@ -502,17 +422,15 @@ void DebugMenuView::on_populate() {
{"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(); }},
- //{"Memory Usage", 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(); }},
{"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push(); }},
- //{ "Radio State", ui::Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ nav_.push(); } },
{"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push(); }},
- {"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push(); }},
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push(); }},
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push(); }},
+ {"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push(); }},
});
- if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
+ if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
add_item(
{"Battery", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_batt_icon, [this]() { nav_.push(); }});
}
diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp
index 25007bbd..6c7b0c43 100644
--- a/firmware/application/apps/ui_debug.hpp
+++ b/firmware/application/apps/ui_debug.hpp
@@ -86,60 +86,13 @@ class DebugMemoryView : public View {
"Done"};
};
-class TemperatureWidget : public Widget {
- public:
- explicit TemperatureWidget(
- Rect parent_rect)
- : Widget{parent_rect} {
- }
-
- void paint(Painter& painter) override;
-
- private:
- using sample_t = uint32_t;
- using temperature_t = int32_t;
-
- temperature_t temperature(const sample_t sensor_value) const;
- Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
-
- std::string temperature_str(const temperature_t temperature) const;
-
- static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
- static constexpr temperature_t display_temp_scale = 3;
- static constexpr int bar_width = 1;
- static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
-};
-
-class TemperatureView : public View {
- public:
- explicit TemperatureView(NavigationView& nav);
-
- void focus() override;
-
- std::string title() const override { return "Temperature"; };
-
- private:
- Text text_title{
- {76, 16, 240, 16},
- "Temperature",
- };
-
- TemperatureWidget temperature_widget{
- {0, 40, 240, 180},
- };
-
- Button button_done{
- {72, 264, 96, 24},
- "Done"};
-};
-
typedef enum {
CT_PMEM,
CT_RFFC5072,
CT_MAX283X,
CT_SI5351,
CT_AUDIO,
- CT_BATTERY,
+ CT_MAX17055,
} chip_type_t;
struct RegistersWidgetConfig {
diff --git a/firmware/application/apps/ui_debug_battery.cpp b/firmware/application/apps/ui_debug_max17055.cpp
similarity index 78%
rename from firmware/application/apps/ui_debug_battery.cpp
rename to firmware/application/apps/ui_debug_max17055.cpp
index 2099d02c..79cd45d5 100644
--- a/firmware/application/apps/ui_debug_battery.cpp
+++ b/firmware/application/apps/ui_debug_max17055.cpp
@@ -1,11 +1,11 @@
-#include "ui_debug_battery.hpp"
+#include "ui_debug_max17055.hpp"
#include "string_format.hpp"
namespace ui {
BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index) {
- if (index < battery::max17055::MAX17055::entries_count) {
- return battery::max17055::MAX17055::entries[index];
+ if (index < i2cdev::I2cDev_MAX17055::entries_count) {
+ return i2cdev::I2cDev_MAX17055::entries[index];
}
return {"", 0, "", 0, false, "", false, 0, false, false, false, 0, false};
}
@@ -27,6 +27,12 @@ BatteryCapacityView::BatteryCapacityView(NavigationView& nav) {
button_done.on_select = [&nav](Button&) { nav.pop(); };
+ auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
+ if (!dev) { // dev not found
+ nav.pop();
+ return;
+ }
+
populate_page(0);
update_page_text();
}
@@ -37,7 +43,7 @@ void BatteryCapacityView::focus() {
bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
int32_t new_page = current_page + delta;
- if (new_page >= 0 && new_page < ((int32_t)battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE) {
+ if (new_page >= 0 && new_page < ((int32_t)i2cdev::I2cDev_MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE) {
current_page = new_page;
populate_page(current_page * ENTRIES_PER_PAGE);
update_page_text();
@@ -46,11 +52,12 @@ bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
}
void BatteryCapacityView::update_values() {
+ i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
size_t entry_index = current_page * ENTRIES_PER_PAGE + i;
- if (entry_index < battery::max17055::MAX17055::entries_count) {
+ if (entry_index < i2cdev::I2cDev_MAX17055::entries_count) {
const auto entry = get_entry(entry_index);
- uint16_t raw_value = battery::BatteryManagement::read_register(entry.address);
+ uint16_t raw_value = dev->read_register(entry.address);
hex_texts[i].set("0x" + to_string_hex(raw_value, 4));
@@ -78,7 +85,7 @@ void BatteryCapacityView::update_values() {
void BatteryCapacityView::populate_page(int start_index) {
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
size_t entry_index = start_index + i;
- if (entry_index < battery::max17055::MAX17055::entries_count) {
+ if (entry_index < i2cdev::I2cDev_MAX17055::entries_count) {
const auto entry = get_entry(entry_index);
name_texts[i].set(entry.name);
addr_texts[i].set("0x" + to_string_hex(entry.address, 2));
@@ -97,7 +104,7 @@ void BatteryCapacityView::populate_page(int start_index) {
}
void BatteryCapacityView::update_page_text() {
- int total_pages = (battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
+ int total_pages = (i2cdev::I2cDev_MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
page_text.set("Page " + to_string_dec_uint(current_page + 1) + "/" + to_string_dec_uint(total_pages));
}
diff --git a/firmware/application/apps/ui_debug_battery.hpp b/firmware/application/apps/ui_debug_max17055.hpp
similarity index 91%
rename from firmware/application/apps/ui_debug_battery.hpp
rename to firmware/application/apps/ui_debug_max17055.hpp
index 3fafa305..10791026 100644
--- a/firmware/application/apps/ui_debug_battery.hpp
+++ b/firmware/application/apps/ui_debug_max17055.hpp
@@ -5,7 +5,8 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "battery.hpp"
-#include "max17055.hpp"
+#include "i2cdevmanager.hpp"
+#include "i2cdev_max17055.hpp"
namespace ui {
@@ -17,7 +18,7 @@ class BatteryCapacityView : public View {
bool on_encoder(const EncoderEvent delta) override;
- using RegisterEntry = battery::max17055::RegisterEntry;
+ using RegisterEntry = i2cdev::I2cDev_MAX17055::RegisterEntry;
private:
static RegisterEntry get_entry(size_t index);
diff --git a/firmware/application/apps/ui_dfu_menu.cpp b/firmware/application/apps/ui_dfu_menu.cpp
index d77d4c92..3bef06e3 100644
--- a/firmware/application/apps/ui_dfu_menu.cpp
+++ b/firmware/application/apps/ui_dfu_menu.cpp
@@ -41,11 +41,6 @@ DfuMenu::DfuMenu(NavigationView& nav)
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
-
- if (battery::BatteryManagement::isDetected()) {
- add_child(&voltage_label);
- add_child(&text_info_line_11);
- }
}
void DfuMenu::paint(Painter& painter) {
@@ -53,7 +48,7 @@ void DfuMenu::paint(Painter& painter) {
size_t m0_fragmented_free_space = 0;
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
- auto lines = (battery::BatteryManagement::isDetected() ? 11 : 10) + 2;
+ auto lines = 10 + 2;
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
@@ -65,9 +60,6 @@ void DfuMenu::paint(Painter& painter) {
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
- if (battery::BatteryManagement::isDetected()) {
- text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6));
- }
constexpr auto margin = 5;
diff --git a/firmware/application/apps/ui_dfu_menu.hpp b/firmware/application/apps/ui_dfu_menu.hpp
index 9bcdb48c..a95fb648 100644
--- a/firmware/application/apps/ui_dfu_menu.hpp
+++ b/firmware/application/apps/ui_dfu_menu.hpp
@@ -60,8 +60,6 @@ class DfuMenu : public View {
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Theme::getInstance()->fg_darkcyan->foreground},
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Theme::getInstance()->fg_darkcyan->foreground}};
- Labels voltage_label{{{6 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "Voltage:", Theme::getInstance()->fg_darkcyan->foreground}};
-
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
@@ -72,7 +70,6 @@ class DfuMenu : public View {
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
- Text text_info_line_11{{15 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
class DfuMenu2 : public View {
diff --git a/firmware/application/apps/ui_encoders.cpp b/firmware/application/apps/ui_encoders.cpp
index 60d386a0..da174f6b 100644
--- a/firmware/application/apps/ui_encoders.cpp
+++ b/firmware/application/apps/ui_encoders.cpp
@@ -25,6 +25,9 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
+#define PADDING_LEFT 1
+#define PADDING_RIGHT 1
+
using namespace portapack;
namespace ui {
@@ -58,6 +61,7 @@ EncodersConfigView::EncodersConfigView(
options_enctype.on_change = [this](size_t index, int32_t) {
on_type_change(index);
+ set_dirty();
};
options_enctype.set_options(enc_options);
@@ -104,7 +108,18 @@ void EncodersConfigView::on_type_change(size_t index) {
// Add new SymFields.
Point pos{2 * 8, 9 * 8};
- std::string format_string;
+ std::string format_string = encoder_def->word_format;
+
+ /*important notes of this format_String
+ * previously before PR#1444, this code using the for loop below to generate the format_string, which MAYBE to prevent showing the S symble.
+ * but after PR#1444, the entireh of symfield code not compatible with this design anymore.
+ * however the encoder_def struct itself has a field called word_format, which exactly be avle to use as the format_string,
+ * because itself is ONLY a hint text, not impact protocol things.
+ * When you find OOK protocol is broken and see this commit from git blame, please be aware that it's not from here.
+ * previously me and @cusspvz already found it broken, this commit is just to fix the broken that from PR#1444.
+ * @zxkmm
+ */
+
uint8_t word_length = encoder_def->word_length;
auto on_change_handler = [this](SymField&) {
generate_frame();
@@ -119,11 +134,9 @@ void EncodersConfigView::on_type_change(size_t index) {
switch (symbol_type) {
case 'A':
symfield->set_symbol_list(encoder_def->address_symbols);
- format_string += 'A';
break;
case 'D':
symfield->set_symbol_list(encoder_def->data_symbols);
- format_string += 'D';
break;
}
@@ -131,8 +144,8 @@ void EncodersConfigView::on_type_change(size_t index) {
pos += Point{8, 0};
}
- // Ugly :( Pad to erase
- format_string.append(24 - format_string.size(), ' ');
+ // cut the S, cuz sync bit isn't in symfield for user to chage/edit.
+ format_string.erase(std::remove(format_string.begin(), format_string.end(), 'S'), format_string.end());
text_format.set(format_string);
generate_frame();
@@ -144,12 +157,34 @@ void EncodersConfigView::on_show() {
}
void EncodersConfigView::draw_waveform() {
+ // padding reason:
+ // in real world the signal would always start with low level and became low level again after yout turn off the radio;
+ // the waveform_buffer only controls drawing, the real send logic that been sent is controlled by frame_fragments
+ // so just for out of looking things
+
size_t length = frame_fragments.length();
- for (size_t n = 0; n < length; n++)
- waveform_buffer[n] = (frame_fragments[n] == '0') ? 0 : 1;
+ // currently not needed since all the supported OOK protocol wont exceed 550 yet
+ if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) {
+ length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT);
+ }
- waveform.set_length(length);
+ // padding l
+ for (size_t i = 0; i < PADDING_LEFT; i++) {
+ waveform_buffer[i] = 0;
+ }
+
+ // real wf
+ for (size_t n = 0; n < length; n++) {
+ waveform_buffer[n + PADDING_LEFT] = (frame_fragments[n] == '0') ? 0 : 1;
+ }
+
+ // padding r
+ for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) {
+ waveform_buffer[i] = 0;
+ }
+
+ waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT);
waveform.set_dirty();
}
diff --git a/firmware/application/apps/ui_encoders.hpp b/firmware/application/apps/ui_encoders.hpp
index e1661891..39879d49 100644
--- a/firmware/application/apps/ui_encoders.hpp
+++ b/firmware/application/apps/ui_encoders.hpp
@@ -32,6 +32,8 @@
#include
#include
+#define WAVEFORM_BUFFER_SIZE 550
+
using namespace encoders;
namespace ui {
@@ -56,7 +58,7 @@ class EncodersConfigView : public View {
std::string frame_fragments = "0";
private:
- int16_t waveform_buffer[550];
+ int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE];
const encoder_def_t* encoder_def{};
void draw_waveform();
diff --git a/firmware/application/apps/ui_external_module_view.cpp b/firmware/application/apps/ui_external_module_view.cpp
new file mode 100644
index 00000000..5e376981
--- /dev/null
+++ b/firmware/application/apps/ui_external_module_view.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui_external_module_view.hpp"
+#include "portapack.hpp"
+#include "ui_standalone_view.hpp"
+
+#include "i2cdevmanager.hpp"
+#include "i2cdev_ppmod.hpp"
+
+#include
+
+namespace ui {
+
+void ExternalModuleView::focus() {
+ dummy.focus();
+}
+
+void ExternalModuleView::on_tick_second() {
+ i2cdev::I2CDevManager::manual_scan();
+
+ auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
+
+ if (!dev) {
+ text_header.set("No module connected");
+ text_name.set("");
+ text_version.set("");
+ text_number_apps.set("");
+ text_app1_name.set("");
+ text_app2_name.set("");
+ text_app3_name.set("");
+ text_app4_name.set("");
+ text_app5_name.set("");
+ return;
+ }
+
+ auto device_info = dev->readDeviceInfo();
+
+ if (device_info.has_value() == false) {
+ text_header.set("No module connected");
+ text_name.set("");
+ text_version.set("");
+ text_number_apps.set("");
+ text_app1_name.set("");
+ text_app2_name.set("");
+ text_app3_name.set("");
+ text_app4_name.set("");
+ text_app5_name.set("");
+ return;
+ }
+
+ text_header.set("Module found");
+
+ std::string btnText = (std::string) "Module: " + device_info->module_name;
+ text_name.set(btnText);
+ text_version.set("Version: " + std::to_string(device_info->module_version));
+ text_number_apps.set("No# Apps: " + std::to_string(device_info->application_count));
+
+ for (uint32_t i = 0; i < device_info->application_count && i < 5; i++) {
+ auto appInfo = dev->getStandaloneAppInfo(i);
+ if (appInfo.has_value() == false) {
+ continue;
+ }
+
+ std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name;
+
+ switch (appInfo->menu_location) {
+ case app_location_t::UTILITIES:
+ btnText += " (Utilities)";
+ break;
+ case app_location_t::RX:
+ btnText += " (RX)";
+ break;
+ case app_location_t::TX:
+ btnText += " (TX)";
+ break;
+ case app_location_t::DEBUG:
+ btnText += " (Debug)";
+ break;
+ case app_location_t::HOME:
+ btnText += " (Home)";
+ break;
+ }
+
+ switch (i) {
+ case 0:
+ text_app1_name.set(btnText);
+ break;
+ case 1:
+ text_app2_name.set(btnText);
+ break;
+ case 2:
+ text_app3_name.set(btnText);
+ break;
+ case 3:
+ text_app4_name.set(btnText);
+ break;
+ case 4:
+ text_app5_name.set(btnText);
+ break;
+ }
+ }
+}
+} // namespace ui
diff --git a/firmware/application/apps/ui_external_module_view.hpp b/firmware/application/apps/ui_external_module_view.hpp
new file mode 100644
index 00000000..f09cf2f2
--- /dev/null
+++ b/firmware/application/apps/ui_external_module_view.hpp
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#ifndef __UI_EXTERNAL_MODULE_VIEW_H
+#define __UI_EXTERNAL_MODULE_VIEW_H
+
+#include "ui.hpp"
+#include "ui_widget.hpp"
+#include "ui_painter.hpp"
+#include "ui_menu.hpp"
+#include "ui_navigation.hpp"
+
+#include "rffc507x.hpp"
+#include "portapack.hpp"
+#include "memory_map.hpp"
+#include "irq_controls.hpp"
+
+#include
+#include
+
+#include "i2cdevmanager.hpp"
+#include "i2cdev_ppmod.hpp"
+
+namespace ui {
+
+class ExternalModuleView : public View {
+ public:
+ ExternalModuleView(NavigationView& nav)
+ : nav_(nav) {
+ add_children({&text_header,
+ &text_name,
+ &text_version,
+ &text_number_apps,
+ &text_app1_name,
+ &text_app2_name,
+ &text_app3_name,
+ &text_app4_name,
+ &text_app5_name,
+ &dummy});
+
+ text_header.set("No module connected");
+
+ signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
+ on_tick_second();
+ };
+ }
+
+ ~ExternalModuleView() {
+ rtc_time::signal_tick_second -= signal_token_tick_second;
+ }
+
+ std::string title() const override { return "Ext Module"; };
+ void focus() override;
+
+ private:
+ NavigationView& nav_;
+ Text text_header{{16, 16, 208, 16}};
+ Text text_name{{24, 32, 200, 16}};
+ Text text_version{{24, 48, 200, 16}};
+ Text text_number_apps{{24, 64, 200, 16}};
+
+ Text text_app1_name{{24, 96, 200, 16}};
+ Text text_app2_name{{24, 112, 200, 16}};
+ Text text_app3_name{{24, 128, 200, 16}};
+ Text text_app4_name{{24, 144, 200, 16}};
+ Text text_app5_name{{24, 160, 200, 16}};
+
+ Button dummy{
+ {240, 0, 0, 0},
+ ""};
+
+ SignalToken signal_token_tick_second{};
+
+ void on_tick_second();
+};
+
+} // namespace ui
+
+#endif
diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp
index 3351cc84..bcc80fd8 100644
--- a/firmware/application/apps/ui_fileman.cpp
+++ b/firmware/application/apps/ui_fileman.cpp
@@ -27,7 +27,6 @@
#include
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
-#include "ui_remote.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_bmp_file_viewer.hpp"
#include "ui_text_editor.hpp"
@@ -704,10 +703,11 @@ bool FileManagerView::handle_file_open() {
reload_current(false);
return true;
- } else if (path_iequal(rem_ext, ext)) {
+ }
+ /*else if (path_iequal(rem_ext, ext)) {
nav_.push(path);
return true;
- }
+ }*/
return false;
}
@@ -751,10 +751,17 @@ FileManagerView::FileManagerView(
text_date.set("Too many files!");
} else {
text_date.set_style(Theme::getInstance()->fg_medium);
- if (selected_is_valid())
- text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
- else
+ if (selected_is_valid()) {
+ if (get_selected_entry().path == str_back) {
+ text_date.set("Go page " + std::to_string(pagination + 1 - 1)); // for better explain, pagination start with 0 AKA real page - 1
+ } else if (get_selected_entry().path == str_next) {
+ text_date.set("Go page " + std::to_string(pagination + 1 + 1)); // when show this, it should display current AKA (pagination + 1) + 1 AKA next page
+ } else {
+ text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
+ }
+ } else {
text_date.set("");
+ }
}
};
diff --git a/firmware/application/apps/ui_flash_utility.cpp b/firmware/application/apps/ui_flash_utility.cpp
index 2eb935f9..55d5a23b 100644
--- a/firmware/application/apps/ui_flash_utility.cpp
+++ b/firmware/application/apps/ui_flash_utility.cpp
@@ -95,7 +95,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
menu_view.add_item({filename.string().substr(0, max_filename_length),
color,
- &bitmap_icon_temperature,
+ &bitmap_icon_peripherals_details,
[this, path](KeyEvent) {
this->firmware_selected(path);
}});
diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp
index 7b6ea5a7..7e230dc6 100644
--- a/firmware/application/apps/ui_freqman.cpp
+++ b/firmware/application/apps/ui_freqman.cpp
@@ -40,27 +40,39 @@ namespace fs = std::filesystem;
// TODO: Clean up after moving to better lookup tables.
using options_t = OptionsField::options_t;
-extern options_t freqman_modulations;
-extern options_t freqman_bandwidths[4];
-extern options_t freqman_steps;
-extern options_t freqman_steps_short;
+
+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_steps_short;
+
+options_t dboptions_to_options(const options_db_t& dboptions) {
+ options_t options;
+ for (const auto& dboption : dboptions) {
+ options.emplace_back(dboption.first, dboption.second);
+ }
+ return options;
+}
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
- option.set_options(freqman_modulations);
+ option.set_options(dboptions_to_options(freqman_modulations));
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
- option.set_options(freqman_bandwidths[modulation]);
+ option.set_options(dboptions_to_options(freqman_bandwidths[modulation]));
}
void freqman_set_step_option(OptionsField& option) {
- option.set_options(freqman_steps);
+ option.set_options(dboptions_to_options(freqman_steps));
}
void freqman_set_step_option_short(OptionsField& option) {
- option.set_options(freqman_steps_short);
+ option.set_options(dboptions_to_options(freqman_steps_short));
}
namespace ui {
@@ -438,7 +450,7 @@ void FrequencyEditView::populate_bandwidth_options() {
auto& bandwidths = freqman_bandwidths[entry_.modulation];
for (auto i = 0u; i < bandwidths.size(); ++i) {
auto& item = bandwidths[i];
- options.push_back({item.first, (OptionsField::value_t)i});
+ options.push_back({(std::string)item.first, (OptionsField::value_t)i});
}
}
@@ -451,7 +463,7 @@ void FrequencyEditView::populate_step_options() {
for (auto i = 0u; i < freqman_steps.size(); ++i) {
auto& item = freqman_steps[i];
- options.push_back({item.first, (OptionsField::value_t)i});
+ options.push_back({(std::string)item.first, (OptionsField::value_t)i});
}
field_step.set_options(std::move(options));
diff --git a/firmware/application/apps/ui_fsk_rx.cpp b/firmware/application/apps/ui_fsk_rx.cpp
deleted file mode 100644
index 17380858..00000000
--- a/firmware/application/apps/ui_fsk_rx.cpp
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_fsk_rx.hpp"
-
-#include "audio.hpp"
-#include "baseband_api.hpp"
-#include "portapack_persistent_memory.hpp"
-#include "string_format.hpp"
-#include "utility.hpp"
-#include "file_path.hpp"
-
-#include "ui_freqman.hpp"
-
-using namespace portapack;
-namespace pmem = portapack::persistent_memory;
-
-void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
- std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
-
- // // Raw hex dump of all the codewords
- // for (size_t c = 0; c < 16; c++)
- // entry += to_string_hex(packet[c], 8) + " ";
-
- log_file.write_entry(data + entry);
-}
-
-void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
- log_file.write_entry(timestamp, text);
-}
-
-namespace ui {
-//---------------------------------------------------------------------------------------------------------------
-// Console View
-//---------------------------------------------------------------------------------------------------------------
-FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
- : View(parent_rect), nav_{nav} {
- add_child(&console);
-};
-
-void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
- if (is_data) {
- console.write(to_string_dec_uint(value) + " ");
- }
-}
-
-void FskRxAppConsoleView::on_show() {
- hidden(false);
-}
-
-void FskRxAppConsoleView::on_hide() {
- hidden(true);
-}
-
-FskRxAppConsoleView::~FskRxAppConsoleView() {
-}
-
-//---------------------------------------------------------------------------------------------------------------
-// Spectrum View
-//---------------------------------------------------------------------------------------------------------------
-FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
- : View(parent_rect), nav_{nav} {
- add_child(&waterfall);
- hidden(true);
-}
-
-FskRxAppView::~FskRxAppView() {
-}
-
-void FskRxAppView::focus() {
-}
-
-void FskRxAppView::on_show() {
- hidden(false);
- waterfall.start();
-}
-
-void FskRxAppView::on_hide() {
- hidden(true);
- waterfall.stop();
-}
-
-//---------------------------------------------------------------------------------------------------------------
-// Base View
-//---------------------------------------------------------------------------------------------------------------
-FskxRxMainView::FskxRxMainView(NavigationView& nav)
- : nav_{nav} {
- add_children({&tab_view,
- &view_data,
- &view_stream,
- &labels,
- &rssi,
- &channel,
- &field_rf_amp,
- &field_lna,
- &field_vga,
- &field_frequency,
- &deviation_frequency,
- &record_view});
-
- baseband::run_image(portapack::spi_flash::image_tag_fskrx);
-
- // DEBUG
- record_view.on_error = [&nav](std::string message) {
- nav.display_modal("Error", message);
- };
-
- deviation_frequency.on_change = [this](rf::Frequency f) {
- refresh_ui(f);
- };
-
- // Set initial sampling rate
- /* Bandwidth of 2FSK is 2 * Deviation */
- record_view.set_sampling_rate(initial_deviation * 2);
-
- field_frequency.set_value(initial_target_frequency);
- deviation_frequency.set_value(initial_deviation);
-
- logger.append(logs_dir / u"FSKRX.TXT");
-
- baseband::set_fsk(initial_deviation);
-
- audio::output::start();
- receiver_model.enable();
-}
-
-void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
- if (logging()) {
- logger.log_decoded(timestamp, prefix);
- }
-}
-
-void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
- /* 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. */
- /* Bandwidth of 2FSK is 2 * Deviation */
- auto sample_rate = deviationHz * 2;
-
- /* base_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. */
-
- if (!view_stream.hidden()) {
- view_stream.waterfall.stop();
- }
-
- // 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);
-
- // Update the radio model with the actual sampling rate.
- receiver_model.set_sampling_rate(actual_sample_rate);
-
- // Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
- auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
- receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
-
- if (!view_stream.hidden()) {
- view_stream.waterfall.start();
- }
-}
-
-void FskxRxMainView::focus() {
- field_frequency.focus();
-}
-
-void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
- View::set_parent_rect(new_parent_rect);
-
- ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
- view_stream.waterfall.set_parent_rect(waterfall_rect);
-}
-
-FskxRxMainView::~FskxRxMainView() {
- audio::output::stop();
- receiver_model.disable();
- baseband::shutdown();
-}
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_fsk_rx.hpp b/firmware/application/apps/ui_fsk_rx.hpp
deleted file mode 100644
index bcebbfb3..00000000
--- a/firmware/application/apps/ui_fsk_rx.hpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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_FSK_RX_H__
-#define __UI_FSK_RX_H__
-
-#include "ui_widget.hpp"
-#include "ui_freq_field.hpp"
-#include "ui_receiver.hpp"
-#include "ui_record_view.hpp"
-#include "ui_rssi.hpp"
-#include "ui_spectrum.hpp"
-#include "ui_tabview.hpp"
-
-#include "app_settings.hpp"
-#include "log_file.hpp"
-#include "radio_state.hpp"
-#include "pocsag_app.hpp"
-
-#include
-
-class FskRxLogger {
- public:
- Optional append(const std::filesystem::path& filename) {
- return log_file.append(filename);
- }
-
- void log_raw_data(const std::string& data, const uint32_t frequency);
- void log_decoded(Timestamp timestamp, const std::string& text);
-
- private:
- LogFile log_file{};
-};
-
-namespace ui {
-class FskRxAppConsoleView : public View {
- public:
- FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
- ~FskRxAppConsoleView();
-
- std::string title() const override { return "FSK RX Data"; };
-
- void on_packet(uint32_t value, bool is_data);
-
- void on_show() override;
- void on_hide() override;
-
- private:
- NavigationView& nav_;
-
- Console console{
- {0, 0, 240, 224}};
-};
-
-class FskRxAppView : public View {
- public:
- FskRxAppView(NavigationView& nav, Rect parent_rect);
- ~FskRxAppView();
-
- void focus() override;
- void on_show() override;
- void on_hide() override;
-
- spectrum::WaterfallView waterfall{};
-
- std::string title() const override { return "FSK RX Stream"; };
-
- private:
- NavigationView& nav_;
- RxRadioState radio_state_{};
-};
-
-class FskxRxMainView : public View {
- public:
- FskxRxMainView(NavigationView& nav);
- ~FskxRxMainView();
-
- void focus() override;
- void set_parent_rect(const Rect new_parent_rect) override;
-
- std::string title() const override { return "FSK RX"; };
-
- private:
- static constexpr uint32_t initial_target_frequency = 902'075'000;
- static constexpr ui::Dim header_height = (5 * 16);
-
- uint32_t initial_deviation{3750};
-
- bool logging() const { return false; };
- bool logging_raw() const { return false; };
-
- NavigationView& nav_;
- Rect view_rect = {0, header_height, 240, 224};
-
- FskRxAppView view_stream{nav_, view_rect};
- FskRxAppConsoleView view_data{nav_, view_rect};
-
- TabView tab_view{
- {"Data", Theme::getInstance()->fg_yellow->foreground, &view_data},
- {"Stream", Theme::getInstance()->fg_cyan->foreground, &view_stream}};
-
- void refresh_ui(rf::Frequency f);
- void on_packet(uint32_t value, bool is_data);
- void handle_decoded(Timestamp timestamp, const std::string& prefix);
-
- uint32_t last_address = 0;
- FskRxLogger logger{};
- uint16_t packet_count = 0;
-
- RxFrequencyField field_frequency{
- {0 * 8, 4 * 8},
- nav_};
-
- RFAmpField field_rf_amp{
- {11 * 8, 2 * 16}};
-
- LNAGainField field_lna{
- {13 * 8, 2 * 16}};
-
- VGAGainField field_vga{
- {16 * 8, 2 * 16}};
-
- RSSI rssi{
- {19 * 8 - 4, 35, 6 * 8, 4}};
-
- Channel channel{
- {19 * 8 - 4, 40, 6 * 8, 4}};
-
- Labels labels{
- {{0 * 8, 3 * 16}, "Deviation:", Theme::getInstance()->fg_light->foreground},
- };
-
- FrequencyField deviation_frequency{
- {10 * 8, 3 * 16},
- {3750, 500000},
- };
-
- // DEBUG
- RecordView record_view{
- {0 * 8, 4 * 16, 30 * 8, 1 * 16},
- u"FSKRX_????.C16",
- u"FSKRX",
- RecordView::FileType::RawS16,
- 16384,
- 3};
-
- MessageHandlerRegistration message_handler_packet{
- Message::ID::AFSKData,
- [this](Message* const p) {
- const auto message = static_cast(p);
- this->view_data.on_packet(message->value, message->is_data);
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__POCSAG_APP_H__*/
\ No newline at end of file
diff --git a/firmware/application/apps/ui_level.cpp b/firmware/application/apps/ui_level.cpp
index a81a97b8..e5aa5ef7 100644
--- a/firmware/application/apps/ui_level.cpp
+++ b/firmware/application/apps/ui_level.cpp
@@ -187,6 +187,7 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
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()) {
diff --git a/firmware/application/apps/ui_looking_glass_app.cpp b/firmware/application/apps/ui_looking_glass_app.cpp
index 7160337a..5e412c42 100644
--- a/firmware/application/apps/ui_looking_glass_app.cpp
+++ b/firmware/application/apps/ui_looking_glass_app.cpp
@@ -98,7 +98,6 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
} else
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
-
return freq_at_pos;
}
@@ -257,7 +256,7 @@ void GlassView::on_range_changed() {
bin_length = SCREEN_W;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
- looking_glass_sampling_rate = looking_glass_bandwidth;
+ looking_glass_sampling_rate = looking_glass_range;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
@@ -265,7 +264,7 @@ void GlassView::on_range_changed() {
// view is made in multiple pass, use original bin picking
mode = scan_type.selected_index_value();
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
- looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
+ looking_glass_sampling_rate = LOOKING_GLASS_MAX_SAMPLERATE;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
@@ -315,8 +314,8 @@ void GlassView::update_min(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
- if (min_size < 2)
- min_size = 2;
+ if (min_size < 1)
+ min_size = 1;
if (v > 7200 - min_size) {
v = 7200 - min_size;
}
@@ -332,8 +331,8 @@ void GlassView::update_max(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
- if (min_size < 2)
- min_size = 2;
+ if (min_size < 1)
+ min_size = 1;
if (v < min_size) {
v = min_size;
}
@@ -487,7 +486,12 @@ GlassView::GlassView(
range_presets.set_selected_index(preset_index);
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
- marker_pixel_index = clip(marker_pixel_index + delta, 0, SCREEN_W);
+ 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;
+ else
+ marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
};
diff --git a/firmware/application/apps/ui_looking_glass_app.hpp b/firmware/application/apps/ui_looking_glass_app.hpp
index 2acfde71..d02803b9 100644
--- a/firmware/application/apps/ui_looking_glass_app.hpp
+++ b/firmware/application/apps/ui_looking_glass_app.hpp
@@ -40,6 +40,7 @@
namespace ui {
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
+#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
#define MHZ_DIV 1000000
// blanked DC (16 centered bins ignored ) and top left and right (2 bins ignored on each side )
diff --git a/firmware/application/apps/ui_numbers.cpp b/firmware/application/apps/ui_numbers.cpp
deleted file mode 100644
index c85bb089..00000000
--- a/firmware/application/apps/ui_numbers.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_numbers.hpp"
-#include "string_format.hpp"
-
-#include "portapack.hpp"
-#include "hackrf_hal.hpp"
-#include "portapack_shared_memory.hpp"
-
-#include
-#include
-
-using namespace portapack;
-
-namespace ui {
-
-// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable)
-
-void NumbersStationView::focus() {
- if (file_error)
- nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT);
- else
- button_exit.focus();
-}
-
-NumbersStationView::~NumbersStationView() {
- transmitter_model.disable();
- baseband::shutdown();
-}
-
-NumbersStationView::wav_file_t* NumbersStationView::get_wav(uint32_t index) {
- return ¤t_voice->available_wavs[index];
-}
-
-void NumbersStationView::prepare_audio() {
- uint8_t code;
- wav_file_t* wav_file;
-
- if (sample_counter >= sample_length) {
- if (segment == ANNOUNCE) {
- if (!announce_loop) {
- code_index = 0;
- segment = MESSAGE;
- } else {
- wav_file = get_wav(11);
- reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav");
- sample_length = wav_file->length;
- announce_loop--;
- }
- }
-
- if (segment == MESSAGE) {
- if (code_index == 25) {
- transmitter_model.disable();
- return;
- }
-
- code = symfield_code.get_offset(code_index);
-
- if (code >= 10) {
- memset(audio_buffer, 0, 1024);
- if (code == 10) {
- pause = 11025; // p: 0.25s @ 44100Hz
- } else if (code == 11) {
- pause = 33075; // P: 0.75s @ 44100Hz
- } else if (code == 12) {
- transmitter_model.disable();
- return;
- }
- } else {
- wav_file = get_wav(code);
- reader->open(current_voice->dir + file_names[code].name + ".wav");
- sample_length = wav_file->length;
- }
- code_index++;
- }
- sample_counter = 0;
- }
-
- if (!pause) {
- auto bytes_read = reader->read(audio_buffer, 1024).value();
-
- // Unsigned to signed, pretty stupid :/
- for (size_t n = 0; n < bytes_read; n++)
- audio_buffer[n] -= 0x80;
- for (size_t n = bytes_read; n < 1024; n++)
- audio_buffer[n] = 0;
-
- sample_counter += 1024;
- } else {
- if (pause >= 1024) {
- pause -= 1024;
- } else {
- sample_counter = sample_length;
- pause = 0;
- }
- }
-
- baseband::set_fifo_data(audio_buffer);
-}
-
-void NumbersStationView::start_tx() {
- // sample_length = sound_sizes[10]; // Announce
- sample_counter = sample_length;
-
- code_index = 0;
- announce_loop = 2;
- segment = ANNOUNCE;
-
- prepare_audio();
-
- transmitter_model.set_rf_amp(true);
- transmitter_model.enable();
-
- baseband::set_audiotx_data(
- (1536000 / 44100) - 1, // TODO: Read wav file's samplerate
- 12000,
- 1,
- false,
- 0);
-}
-
-void NumbersStationView::on_tick_second() {
- armed_blink = not armed_blink;
-
- if (armed_blink)
- check_armed.set_style(Theme::getInstance()->fg_red);
- else
- check_armed.set_style(&style());
-
- check_armed.set_dirty();
-}
-
-void NumbersStationView::on_voice_changed(size_t index) {
- std::string code_list;
-
- for (const auto& wavs : voices[index].available_wavs)
- code_list += wavs.code;
-
- symfield_code.set_symbol_list(code_list);
- current_voice = &voices[index];
-}
-
-bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) {
- if (reader->open("/numbers/" + dir + "/" + file)) {
- // Check format (mono, 8 bits)
- if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
- return true;
- else
- return false;
- } else
- return false;
-}
-
-NumbersStationView::NumbersStationView(
- NavigationView& nav)
- : nav_(nav) {
- std::vector directory_list;
- using option_t = std::pair;
- using options_t = std::vector;
- options_t voice_options;
- voice_t temp_voice{};
- bool valid;
- uint32_t c;
- // uint8_t y, m, d, dayofweek;
-
- reader = std::make_unique();
-
- // Search for valid voice directories
- directory_list = scan_root_directories("/numbers");
- if (!directory_list.size()) {
- file_error = true;
- return;
- }
-
- for (const auto& dir : directory_list) {
- c = 0;
- for (const auto& file_name : file_names) {
- valid = check_wav_validity(dir.string(), file_name.name + ".wav");
- if ((!valid) && (file_name.required)) {
- temp_voice.available_wavs.clear();
- break; // Invalid required file means invalid voice
- } else if (valid) {
- temp_voice.available_wavs.push_back({file_name.code, c++, 0, 0}); // TODO: Needs length and samplerate
- }
- }
- if (!temp_voice.available_wavs.empty()) {
- // Voice can be used, are there accent files ?
- c = 0;
- for (const auto& file_name : file_names) {
- valid = check_wav_validity(dir.string(), file_name.name + "a.wav");
- if ((!valid) && (file_name.required)) {
- c = 0;
- break; // Invalid required file means accents can't be used
- } else if (valid) {
- c++;
- }
- }
-
- temp_voice.accent = c ? true : false;
- temp_voice.dir = dir.string();
-
- voices.push_back(temp_voice);
- }
- }
-
- if (voices.empty()) {
- file_error = true;
- return;
- }
-
- baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
-
- add_children({&labels,
- &symfield_code,
- &check_armed,
- &options_voices,
- &text_voice_flags,
- //&button_tx_now,
- &button_exit});
-
- for (const auto& voice : voices)
- voice_options.emplace_back(voice.dir.substr(0, 4), 0);
-
- options_voices.set_options(voice_options);
- options_voices.on_change = [this](size_t i, int32_t) {
- this->on_voice_changed(i);
- };
- options_voices.set_selected_index(0);
-
- check_armed.set_value(false);
-
- check_armed.on_select = [this](Checkbox&, bool v) {
- if (v) {
- armed_blink = false;
- signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
- this->on_tick_second();
- };
- } else {
- check_armed.set_style(&style());
- rtc_time::signal_tick_second -= signal_token_tick_second;
- }
- };
-
- // DEBUG
- symfield_code.set_offset(0, 10);
- symfield_code.set_offset(1, 3);
- symfield_code.set_offset(2, 4);
- symfield_code.set_offset(3, 11);
- symfield_code.set_offset(4, 6);
- symfield_code.set_offset(5, 1);
- symfield_code.set_offset(6, 9);
- symfield_code.set_offset(7, 7);
- symfield_code.set_offset(8, 8);
- symfield_code.set_offset(9, 0);
- symfield_code.set_offset(10, 12); // End
-
- /*
- dayofweek = rtc_time::current_day_of_week();
- text_title.set(day_of_week[dayofweek]);
- */
-
- button_exit.on_select = [&nav](Button&) {
- nav.pop();
- };
-}
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_numbers.hpp b/firmware/application/apps/ui_numbers.hpp
deleted file mode 100644
index bf2cd292..00000000
--- a/firmware/application/apps/ui_numbers.hpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_NUMBERS_H__
-#define __UI_NUMBERS_H__
-
-#include "ui.hpp"
-#include "ui_widget.hpp"
-#include "ui_receiver.hpp"
-#include "ui_navigation.hpp"
-#include "rtc_time.hpp"
-#include "clock_manager.hpp"
-#include "baseband_api.hpp"
-#include "utility.hpp"
-#include "message.hpp"
-#include "file.hpp"
-#include "io_wave.hpp"
-#include "radio_state.hpp"
-
-namespace ui {
-
-class NumbersStationView : public View {
- public:
- NumbersStationView(NavigationView& nav);
- ~NumbersStationView();
-
- NumbersStationView(const NumbersStationView&) = delete;
- NumbersStationView(NumbersStationView&&) = delete;
- NumbersStationView& operator=(const NumbersStationView&) = delete;
- NumbersStationView& operator=(NumbersStationView&&) = delete;
-
- void focus() override;
-
- std::string title() const override { return "Station"; };
-
- private:
- NavigationView& nav_;
-
- TxRadioState radio_state_{
- 0 /* frequency */,
- 1750000 /* bandwidth */,
- 1536000 /* sampling rate */
- };
-
- // Sequencing state machine
- enum segments {
- IDLE = 0,
- ANNOUNCE,
- MESSAGE,
- SIGNOFF
- };
-
- typedef struct {
- char code;
- uint32_t index;
- uint32_t length;
- uint32_t samplerate;
- } wav_file_t;
-
- struct voice_t {
- std::string dir;
- std::vector available_wavs;
- bool accent;
- };
-
- std::vector voices{};
- voice_t* current_voice{};
-
- struct wav_file_list_t {
- std::string name;
- bool required;
- char code;
- };
-
- const std::vector file_names = {
- {"0", true, '0'},
- {"1", true, '1'},
- {"2", true, '2'},
- {"3", true, '3'},
- {"4", true, '4'},
- {"5", true, '5'},
- {"6", true, '6'},
- {"7", true, '7'},
- {"8", true, '8'},
- {"9", true, '9'},
- {"announce", false, 'A'}};
-
- segments segment{IDLE};
- bool armed{false};
- bool file_error{false};
-
- // const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
- // const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
-
- std::unique_ptr reader{};
-
- uint8_t code_index{0}, announce_loop{0};
- uint32_t sample_counter{0};
- uint32_t sample_length{0};
- int8_t audio_buffer[1024]{};
- uint32_t pause{0};
- bool armed_blink{false};
- SignalToken signal_token_tick_second{};
-
- wav_file_t* get_wav(uint32_t index);
- bool check_wav_validity(const std::string dir, const std::string file);
- void on_voice_changed(size_t index);
- void on_tick_second();
- void prepare_audio();
- void start_tx();
-
- Labels labels{
- {{2 * 8, 5 * 8}, "Voice: Flags:", Theme::getInstance()->fg_light->foreground},
- {{1 * 8, 8 * 8}, "Code:", Theme::getInstance()->fg_light->foreground}};
-
- OptionsField options_voices{
- {8 * 8, 1 * 8},
- 4,
- {}};
- Text text_voice_flags{
- {19 * 8, 1 * 8, 2 * 8, 16},
- ""};
-
- SymField symfield_code{
- {1 * 8, 10 * 8},
- 25,
- SymField::Type::Custom};
-
- Checkbox check_armed{
- {2 * 8, 13 * 16},
- 5,
- "Armed"};
-
- /*
- Button button_tx_now {
- { 18 * 8, 13 * 16, 10 * 8, 32 },
- "TX now"};
- */
-
- Button button_exit{
- {21 * 8, 16 * 16, 64, 32},
- "Exit"};
-
- MessageHandlerRegistration message_handler_fifo_signal{
- Message::ID::RequestSignal,
- [this](const Message* const p) {
- const auto message = static_cast(p);
- if (message->signal == RequestSignalMessage::Signal::FillRequest) {
- this->prepare_audio();
- }
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__UI_NUMBERS_H__*/
diff --git a/firmware/application/apps/ui_nuoptix.cpp b/firmware/application/apps/ui_nuoptix.cpp
deleted file mode 100644
index e4894f2c..00000000
--- a/firmware/application/apps/ui_nuoptix.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_nuoptix.hpp"
-
-#include "ch.h"
-#include "portapack.hpp"
-#include "lfsr_random.hpp"
-#include "string_format.hpp"
-
-#include "portapack_shared_memory.hpp"
-
-#include
-#include
-
-using namespace portapack;
-
-namespace ui {
-
-void NuoptixView::focus() {
- number_timecode.focus();
-}
-
-NuoptixView::~NuoptixView() {
- transmitter_model.disable();
- baseband::shutdown();
-}
-
-void NuoptixView::on_tx_progress(const uint32_t progress, const bool done) {
- if (done)
- transmit(false);
- else
- progressbar.set_value(progress);
-}
-
-void NuoptixView::transmit(bool setup) {
- uint8_t mod, tone_code;
- uint8_t c;
- uint8_t dtmf_message[6];
- rtc::RTC datetime;
-
- if (!tx_mode) {
- transmitter_model.disable();
- return;
- }
-
- if (tx_mode == IMPROVISE)
- timecode = lfsr_iterate(timecode) % 1999; // Could be 9999 but that would be one long audio track !
-
- if (setup) {
- progressbar.set_max(6 * 2);
-
- if (tx_mode == IMPROVISE) {
- // Seed from RTC
- rtcGetTime(&RTCD1, &datetime);
- timecode = datetime.day() + datetime.second();
- } else {
- timecode = number_timecode.value();
- }
-
- transmitter_model.set_rf_amp(true);
- transmitter_model.enable();
-
- dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
- dtmf_message[1] = 'A'; // "Restart" method #1
- } else {
- dtmf_message[0] = '#';
- dtmf_message[1] = (timecode / 1000) % 10;
- chThdSleepMilliseconds(92); // 141-49ms
- number_timecode.set_value(timecode);
- }
-
- progressbar.set_value(0);
-
- dtmf_message[2] = (timecode / 100) % 10;
- dtmf_message[3] = (timecode / 10) % 10;
- dtmf_message[4] = timecode % 10;
-
- mod = 0;
- for (c = 1; c < 5; c++)
- if (dtmf_message[c] <= 9)
- mod += dtmf_message[c];
-
- mod = 10 - (mod % 10);
- if (mod == 10) mod = 0; // Is this right ?
-
- text_mod.set("Mod: " + to_string_dec_uint(mod));
-
- dtmf_message[5] = mod;
-
- for (c = 0; c < 6; c++) {
- tone_code = dtmf_message[c];
-
- if (tone_code == 'A')
- tone_code = 10;
- else if (tone_code == 'B')
- tone_code = 11;
- else if (tone_code == 'C')
- tone_code = 12;
- else if (tone_code == 'D')
- tone_code = 13;
- else if (tone_code == '#')
- tone_code = 14;
- else if (tone_code == '*')
- tone_code = 15;
-
- shared_memory.bb_data.tones_data.message[c * 2] = tone_code;
- shared_memory.bb_data.tones_data.message[c * 2 + 1] = 0xFF; // Silence
- }
-
- for (c = 0; c < 16; c++) {
- baseband::set_tone(c * 2, dtmf_deltas[c][0], NUOPTIX_TONE_LENGTH);
- baseband::set_tone(c * 2 + 1, dtmf_deltas[c][1], NUOPTIX_TONE_LENGTH);
- }
- shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
-
- audio::set_rate(audio::Rate::Hz_24000);
-
- baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
-
- timecode++;
-}
-
-NuoptixView::NuoptixView(
- NavigationView& nav) {
- baseband::run_image(portapack::spi_flash::image_tag_tones);
-
- add_children({&number_timecode,
- &text_timecode,
- &text_mod,
- &progressbar,
- &tx_view});
-
- number_timecode.set_value(1);
-
- tx_view.on_edit_frequency = [this, &nav]() {
- auto new_view = nav.push(transmitter_model.target_frequency());
- new_view->on_changed = [this](rf::Frequency f) {
- transmitter_model.set_target_frequency(f);
- };
- };
-
- tx_view.on_start = [this]() {
- tx_view.set_transmitting(true);
- tx_mode = NORMAL;
- transmit(true);
- };
-
- tx_view.on_stop = [this]() {
- tx_view.set_transmitting(false);
- tx_mode = IDLE;
- };
-
- /*button_impro.on_select = [this](Button&){
- if (tx_mode == IMPROVISE) {
- tx_mode = IDLE;
- button_impro.set_text("IMPROVISE");
- } else if (tx_mode == IDLE) {
- tx_mode = IMPROVISE;
- button_impro.set_text("STOP");
- transmit(true);
- }
- };*/
-}
-
-} // namespace ui
diff --git a/firmware/application/apps/ui_nuoptix.hpp b/firmware/application/apps/ui_nuoptix.hpp
deleted file mode 100644
index bc7564ca..00000000
--- a/firmware/application/apps/ui_nuoptix.hpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_NUOPTIX_H__
-#define __UI_NUOPTIX_H__
-
-#include "ui.hpp"
-#include "ui_widget.hpp"
-#include "baseband_api.hpp"
-#include "ui_navigation.hpp"
-#include "ui_transmitter.hpp"
-#include "rtc_time.hpp"
-#include "tonesets.hpp"
-#include "message.hpp"
-#include "volume.hpp"
-#include "audio.hpp"
-#include "radio_state.hpp"
-
-#define NUOPTIX_TONE_LENGTH ((TONES_SAMPLERATE * 0.049) - 1) // 49ms
-
-namespace ui {
-
-class NuoptixView : public View {
- public:
- NuoptixView(NavigationView& nav);
- ~NuoptixView();
-
- void focus() override;
-
- std::string title() const override { return "Nuoptix sync"; };
-
- private:
- enum tx_modes {
- IDLE = 0,
- NORMAL,
- IMPROVISE
- };
-
- TxRadioState radio_state_{
- 0 /* frequency */,
- 1750000 /* bandwidth */,
- 1536000 /* sampling rate */
- };
-
- tx_modes tx_mode{IDLE};
-
- void transmit(bool setup);
- void on_tx_progress(const uint32_t progress, const bool done);
-
- uint32_t timecode{0};
-
- Text text_timecode{
- {10 * 8, 2 * 16, 9 * 8, 16},
- "Timecode:"};
-
- NumberField number_timecode{
- {13 * 8, 3 * 16},
- 4,
- {1, 9999},
- 1,
- '0'};
-
- Text text_mod{
- {10 * 8, 5 * 16, 6 * 8, 16},
- "Mod: "};
-
- ProgressBar progressbar{
- {16, 14 * 16, 208, 16}};
-
- /*Button button_impro {
- { 64, 184, 112, 40 },
- "IMPROVISE"
- };*/
-
- TransmitterView tx_view{
- 16 * 16,
- 10000,
- 15};
-
- MessageHandlerRegistration message_handler_tx_progress{
- Message::ID::TXProgress,
- [this](const Message* const p) {
- const auto message = *reinterpret_cast(p);
- this->on_tx_progress(message.progress, message.done);
- }};
-};
-
-} /* namespace ui */
-
-#endif /*__UI_NUOPTIX_H__*/
diff --git a/firmware/application/apps/ui_pocsag_tx.cpp b/firmware/application/apps/ui_pocsag_tx.cpp
index b9bc06d7..2efed265 100644
--- a/firmware/application/apps/ui_pocsag_tx.cpp
+++ b/firmware/application/apps/ui_pocsag_tx.cpp
@@ -33,6 +33,8 @@ using namespace pocsag;
namespace ui {
+#define MAX_POCSAG_LENGTH 80
+
void POCSAGTXView::focus() {
field_address.focus();
}
@@ -57,6 +59,7 @@ void POCSAGTXView::on_remote(const PocsagTosendMessage data) {
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
field_address.set_value(data.addr);
message = (char*)data.msg;
+ buffer = message;
text_message.set(message);
options_bitrate.dirty();
options_type.dirty();
@@ -105,9 +108,9 @@ bool POCSAGTXView::start_tx() {
progressbar.set_max(total_frames);
- transmitter_model.set_rf_amp(true);
- transmitter_model.set_tx_gain(40);
- // We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
+ // transmitter_model.set_rf_amp(true);
+ // transmitter_model.set_tx_gain(40);
+ // We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
transmitter_model.enable();
@@ -139,11 +142,18 @@ bool POCSAGTXView::start_tx() {
void POCSAGTXView::paint(Painter&) {
message = buffer;
- text_message.set(message);
+ text_message.set(message); // the whole message, but it may not fit.
+ if (message.length() > 30 && message.length() <= 60) {
+ text_message_l2.set(message.substr(29)); // remaining to 2nd line
+ } else if (message.length() > 60) {
+ text_message_l2.set(message.substr(29, 27) + "...");
+ } else {
+ text_message_l2.set("");
+ }
}
void POCSAGTXView::on_set_text(NavigationView& nav) {
- text_prompt(nav, buffer, 30);
+ text_prompt(nav, buffer, MAX_POCSAG_LENGTH);
}
POCSAGTXView::POCSAGTXView(
@@ -158,6 +168,7 @@ POCSAGTXView::POCSAGTXView(
&options_function,
&options_phase,
&text_message,
+ &text_message_l2,
&button_message,
&progressbar,
&tx_view});
diff --git a/firmware/application/apps/ui_pocsag_tx.hpp b/firmware/application/apps/ui_pocsag_tx.hpp
index de0240ae..f4eb242c 100644
--- a/firmware/application/apps/ui_pocsag_tx.hpp
+++ b/firmware/application/apps/ui_pocsag_tx.hpp
@@ -122,15 +122,18 @@ class POCSAGTXView : public View {
}};
Text text_message{
- {0 * 8, 16 * 8, 16 * 8, 16},
+ {0 * 8, 16 * 8, 30 * 8, 16},
+ ""};
+ Text text_message_l2{
+ {0 * 8, 18 * 8, 30 * 8, 16},
""};
Button button_message{
- {0 * 8, 18 * 8, 14 * 8, 32},
+ {0 * 8, 20 * 8, 14 * 8, 32},
"Set message"};
ProgressBar progressbar{
- {16, 200, 208, 16}};
+ {16, 210, 208, 16}};
TransmitterView tx_view{
16 * 16,
diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp
index 48879ca1..977f9cbb 100644
--- a/firmware/application/apps/ui_recon.cpp
+++ b/firmware/application/apps/ui_recon.cpp
@@ -28,7 +28,6 @@
#include "file.hpp"
#include "file_reader.hpp"
#include "tone_key.hpp"
-#include "replay_app.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
diff --git a/firmware/application/apps/ui_script.cpp b/firmware/application/apps/ui_script.cpp
deleted file mode 100644
index a9717195..00000000
--- a/firmware/application/apps/ui_script.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_script.hpp"
-
-#include "portapack.hpp"
-#include "event_m0.hpp"
-
-#include
-
-using namespace portapack;
-
-namespace ui {
-
-void ScriptView::on_frequency_select() {
- // button_edit_freq.focus();
-}
-
-void ScriptView::on_edit_freq(rf::Frequency f) {
- (void)f;
- // frequencies[menu_view.highlighted()].value = f;
- setup_list();
-}
-
-void ScriptView::on_edit_desc(NavigationView& nav) {
- (void)nav;
-}
-
-void ScriptView::on_delete() {
- // frequencies.erase(frequencies.begin() + menu_view.highlighted());
- setup_list();
-}
-
-void ScriptView::setup_list() {
- // size_t n;
-
- menu_view.clear();
-
- /*for (n = 0; n < frequencies.size(); n++) {
- menu_view.add_item({ freqman_item_string(frequencies[n]), Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ on_frequency_select(); } });
- }*/
-
- menu_view.set_parent_rect({0, 0, 240, 168});
- menu_view.set_highlighted(menu_view.highlighted()); // Refresh
-}
-
-void ScriptView::focus() {
- menu_view.focus();
-}
-
-ScriptView::ScriptView(
- NavigationView& nav) {
- add_children({&menu_view,
- &text_edit,
- &button_edit_freq,
- &button_edit_desc,
- &button_del,
- &button_exit});
-
- setup_list();
-
- button_edit_freq.on_select = [this, &nav](Button&) {
- /*auto new_view = nav.push(frequencies[menu_view.highlighted()].value);
- new_view->on_changed = [this](rf::Frequency f) {
- on_edit_freq(f);
- };*/
- };
-
- button_edit_desc.on_select = [this, &nav](Button&) {
- on_edit_desc(nav);
- };
-
- button_del.on_select = [this, &nav](Button&) {
- nav.push("Confirm", "Are you sure?", YESNO,
- [this](bool choice) {
- if (choice) {
- on_delete();
- }
- });
- };
-
- button_exit.on_select = [this, &nav](Button&) {
- nav.pop();
- };
-}
-
-} // namespace ui
diff --git a/firmware/application/apps/ui_script.hpp b/firmware/application/apps/ui_script.hpp
deleted file mode 100644
index 0add5de8..00000000
--- a/firmware/application/apps/ui_script.hpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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.hpp"
-#include "ui_widget.hpp"
-#include "ui_painter.hpp"
-#include "ui_menu.hpp"
-#include "ui_navigation.hpp"
-#include "ui_receiver.hpp"
-#include "ui_textentry.hpp"
-#include "rtc_time.hpp"
-
-namespace ui {
-
-enum script_keyword {
- STOP = 0,
- WAIT_N,
- WAIT_RTC,
- IF,
- LOOP,
- END,
- TX,
- RX
-};
-
-struct script_line {
- script_keyword keyword;
-};
-
-class ScriptView : public View {
- public:
- ScriptView(NavigationView& nav);
-
- void focus() override;
-
- std::string title() const override { return "Script editor"; };
-
- private:
- void on_frequency_select();
- void on_edit_freq(rf::Frequency f);
- void on_edit_desc(NavigationView& nav);
- void on_delete();
- void setup_list();
-
- std::vector script{};
-
- MenuView menu_view{
- {0, 0, 240, 168},
- true};
-
- Text text_edit{
- {16, 194, 5 * 8, 16},
- "Edit:"};
- Button button_edit_freq{
- {16, 194 + 16, 88, 32},
- "Frequency"};
- Button button_edit_desc{
- {16, 194 + 16 + 34, 88, 32},
- "Description"};
- Button button_del{
- {160, 192, 72, 64},
- "Delete"};
-
- Button button_exit{
- {160, 264, 72, 32},
- "Exit"};
-};
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp
index 3988014e..3af587dc 100644
--- a/firmware/application/apps/ui_settings.cpp
+++ b/firmware/application/apps/ui_settings.cpp
@@ -5,6 +5,7 @@
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
+ * Copyright (C) 2024 HTotoo
* Copyleft (ɔ) 2024 zxkmm under GPL license
*
* This file is part of PortaPack.
@@ -49,6 +50,8 @@ namespace fs = std::filesystem;
#include "ui_font_fixed_8x16.hpp"
#include "cpld_update.hpp"
#include "config_mode.hpp"
+#include "i2cdevmanager.hpp"
+#include "i2cdev_max17055.hpp"
extern ui::SystemView* system_view_ptr;
@@ -321,7 +324,6 @@ SetUIView::SetUIView(NavigationView& nav) {
&toggle_bias_tee,
&toggle_clock,
&toggle_mute,
- &toggle_fake_brightness,
&toggle_sd_card,
&button_save,
&button_cancel});
@@ -359,7 +361,6 @@ 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());
@@ -388,7 +389,6 @@ 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());
@@ -777,20 +777,13 @@ void SetConfigModeView::focus() {
/* SetDisplayView ************************************/
SetDisplayView::SetDisplayView(NavigationView& nav) {
- add_children({&labels,
- &field_fake_brightness,
- &button_save,
+ add_children({&button_save,
&button_cancel,
- &checkbox_invert_switch,
- &checkbox_brightness_switch});
+ &checkbox_invert_switch});
- field_fake_brightness.set_by_value(pmem::fake_brightness_level());
- checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
button_save.on_select = [&nav, this](Button&) {
- pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
- pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
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());
@@ -798,13 +791,6 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
send_system_refresh();
nav.pop();
};
- // only enable invert OR fake brightness
- checkbox_invert_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_invert_switch.set_value(false);
- };
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
@@ -815,6 +801,107 @@ void SetDisplayView::focus() {
button_save.focus();
}
+/* SetTouchscreenSensitivityView ************************************/
+/* sample max: 1023 sample_t AKA uint16_t
+ * touch_sensitivity: range: 1 to 128
+ * threshold = 1023 / sensitive
+ * threshold range: 1023/1 to 1023/128 = 1023 to 8
+ */
+SetTouchscreenThresholdView::SetTouchscreenThresholdView(NavigationView& nav) {
+ add_children({&labels,
+ &field_threshold,
+ &button_autodetect,
+ &button_reset,
+ &button_save,
+ &button_cancel,
+ &text_hint,
+ &text_wait_timer});
+
+ set_dirty();
+ org_threshold = portapack::touch_threshold;
+ field_threshold.set_value(pmem::touchscreen_threshold());
+ text_hint.set_style(Theme::getInstance()->error_dark);
+ text_hint.hidden(true);
+ text_wait_timer.set_style(Theme::getInstance()->error_dark);
+ text_wait_timer.hidden(true);
+ // clang-format off
+ button_autodetect.on_select = [this, &nav](Button&) {
+ nav.display_modal("NOTICE",
+ "Now on don't touch screen;\n"
+ "Use arrow keys to operate.\n"
+ "Follow instructions.\n"
+ "Press YES to continue",
+ YESNO, [this, &nav](bool choice) {
+ if (choice){
+ time_start_auto_detect = chTimeNow();
+ text_hint.hidden(false);
+ text_wait_timer.hidden(false);
+ text_wait_timer.set("ETA " + to_string_dec_uint(10) + "s");
+ in_auto_detect = true;
+ field_threshold.set_value(1);
+ portapack::touch_threshold = 1;
+ set_dirty(); } }, TRUE);
+ };
+ // clang-format on
+
+ button_reset.on_select = [this](Button&) {
+ field_threshold.set_value(32);
+ portapack::touch_threshold = 32;
+ };
+
+ button_save.on_select = [&nav, this](Button&) {
+ pmem::set_touchscreen_threshold(field_threshold.value());
+ portapack::touch_threshold = field_threshold.value();
+ send_system_refresh();
+ nav.pop();
+ };
+
+ button_cancel.on_select = [&nav, this](Button&) {
+ portapack::touch_threshold = org_threshold;
+ nav.pop();
+ };
+}
+
+void SetTouchscreenThresholdView::focus() {
+ button_autodetect.focus();
+ set_dirty();
+}
+
+void SetTouchscreenThresholdView::on_frame_sync() {
+ if (!in_auto_detect) return;
+ uint32_t time_now = chTimeNow();
+ int32_t time_diff = time_now - time_start_auto_detect;
+ text_wait_timer.set("ETA " + to_string_dec_uint((10 - time_diff / 1000) <= 0 ? 0 : 10 - time_diff / 1000) + "s");
+ if (time_diff >= 10001 && !auto_detect_succeed_consumed) { // 10s
+ in_auto_detect = false;
+ text_wait_timer.hidden(true);
+ text_hint.set("OK, press save and reboot");
+ portapack::touch_threshold = org_threshold;
+ pmem::set_touchscreen_threshold(org_threshold);
+ set_dirty();
+ auto_detect_succeed_consumed = true;
+ button_save.focus();
+ return;
+ }
+ if (get_touch_frame().touch) {
+ if (in_auto_detect) {
+ uint16_t sen = field_threshold.value();
+ sen++;
+ portapack::touch_threshold = sen;
+ field_threshold.set_value(sen);
+ }
+ }
+}
+
+SetTouchscreenThresholdView::~SetTouchscreenThresholdView() {
+ // it seems that sometimes in the msg handler func it would enter the condi that not possible to entered,
+ // so added this workaround.
+ // TODO: find out why
+ in_auto_detect = false;
+ auto_detect_succeed_consumed = false;
+ time_start_auto_detect = 0;
+}
+
/* SetMenuColorView ************************************/
void SetMenuColorView::paint_sample() {
@@ -876,6 +963,7 @@ SetAutostartView::SetAutostartView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
+ &button_reset,
&options});
button_save.on_select = [&nav, this](Button&) {
@@ -892,6 +980,12 @@ SetAutostartView::SetAutostartView(NavigationView& nav) {
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};
@@ -969,7 +1063,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
&button_cancel,
&checkbox_overridebatt});
- if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BATT_MAX17055) add_children({&button_reset, &labels2});
+ 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());
@@ -979,7 +1073,8 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
};
button_reset.on_select = [&nav, this](Button&) {
- if (battery::BatteryManagement::reset_learned())
+ auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
+ if (dev->reset_learned())
nav.display_modal("Reset", "Battery parameters reset");
else
nav.display_modal("Error", "Error parameter reset");
@@ -1011,6 +1106,7 @@ void SettingsMenuView::on_populate() {
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push(); }},
+ {"TouchThreshold", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push(); }},
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push(); }},
{"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(); }},
diff --git a/firmware/application/apps/ui_settings.hpp b/firmware/application/apps/ui_settings.hpp
index 4a94d90c..9d31429d 100644
--- a/firmware/application/apps/ui_settings.hpp
+++ b/firmware/application/apps/ui_settings.hpp
@@ -35,6 +35,7 @@
#include "bitmap.hpp"
#include "ff.h"
#include "portapack_persistent_memory.hpp"
+#include "irq_controls.hpp"
#include
@@ -362,12 +363,8 @@ class SetUIView : public View {
{19 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_batt_text};
- 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},
+ {21 * 8, 14 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
@@ -709,8 +706,6 @@ class SetConfigModeView : public View {
};
};
-using portapack::persistent_memory::fake_brightness_level_options;
-
class SetDisplayView : public View {
public:
SetDisplayView(NavigationView& nav);
@@ -720,27 +715,8 @@ class SetDisplayView : public View {
std::string title() const override { return "Display"; };
private:
- 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},
- };
-
- 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_invert_switch{
- {1 * 8, 10 * 16},
+ {1 * 8, 2 * 16},
23,
"Invert colors (For IPS)"};
@@ -754,6 +730,77 @@ class SetDisplayView : public View {
};
};
+using portapack::persistent_memory::touchscreen_threshold;
+
+class SetTouchscreenThresholdView : public View {
+ public:
+ SetTouchscreenThresholdView(NavigationView& nav);
+ ~SetTouchscreenThresholdView();
+
+ void focus() override;
+
+ std::string title() const override { return "Touch S"; };
+
+ private:
+ bool in_auto_detect = false;
+ uint16_t org_threshold = 0;
+ uint8_t auto_detect_succeed_consumed = false; // prevent screen flash but can still change text content
+ uint32_t time_start_auto_detect = 0;
+
+ Labels labels{
+ {{1 * 8, 1 * 16}, "Set touchscreen sensitivity", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 2 * 16}, "Or press auto detect button", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 3 * 16}, "FOLLOW INSTRUCTIONS", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 4 * 16}, "REBOOT TO APPLY", Theme::getInstance()->fg_light->foreground},
+ {{1 * 8, 11 * 16}, "Threshold:", Theme::getInstance()->fg_light->foreground},
+ };
+
+ Text text_hint{
+ {1 * 8, 7 * 16, screen_width - 2 * 8, 1 * 16},
+ "DON'T TOUCH SCREEN"};
+
+ Text text_wait_timer{
+ {1 * 8, 8 * 16, screen_width - 2 * 8, 1 * 16},
+ "ETA 00:00"};
+
+ void on_frame_sync();
+
+ /* sample max: 1023 sample_t AKA uint16_t
+ * touch_sensitivity: range: 1 to 128
+ * threshold range: 1023/1 to 1023/128 = 1023 to 8
+ */
+ NumberField field_threshold{
+ {1 * 8 + 11 * 8 + 8, 11 * 16},
+ 4,
+ {1, 1023},
+ 1,
+ ' ',
+ };
+
+ Button button_autodetect{
+ {2 * 8, 13 * 16, 12 * 8, 32},
+ "Auto Detect"};
+ Button button_reset{
+ {16 * 8, 13 * 16, 12 * 8, 32},
+ "Reset",
+ };
+
+ Button button_save{
+ {2 * 8, 16 * 16, 12 * 8, 32},
+ "Save"};
+
+ Button button_cancel{
+ {16 * 8, 16 * 16, 12 * 8, 32},
+ "Cancel",
+ };
+
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ this->on_frame_sync();
+ }};
+};
+
class SetMenuColorView : public View {
public:
SetMenuColorView(NavigationView& nav);
@@ -843,14 +890,19 @@ class SetAutostartView : public View {
"Save"};
OptionsField options{
- {8 * 8, 4 * 16},
- 30,
- {}};
+ {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 {
@@ -872,15 +924,16 @@ class SetThemeView : public View {
"Save"};
OptionsField options{
- {8 * 8, 4 * 16},
- 30,
+ {0 * 8, 4 * 16},
+ screen_width / 8,
{
{"Default - Grey", 0},
{"Yellow", 1},
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
- }};
+ },
+ true};
Checkbox checkbox_menuset{
{2 * 8, 6 * 16},
diff --git a/firmware/application/apps/ui_sigfrx.cpp b/firmware/application/apps/ui_sigfrx.cpp
index 750cf9c6..e0493306 100644
--- a/firmware/application/apps/ui_sigfrx.cpp
+++ b/firmware/application/apps/ui_sigfrx.cpp
@@ -56,7 +56,7 @@ SIGFRXView::~SIGFRXView() {
void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
- // portapack::display.drawBMP({0, 302-160}, fox_bmp);
+ // 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);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
diff --git a/firmware/application/apps/ui_ss_viewer.cpp b/firmware/application/apps/ui_ss_viewer.cpp
index eb14fda8..a0741041 100644
--- a/firmware/application/apps/ui_ss_viewer.cpp
+++ b/firmware/application/apps/ui_ss_viewer.cpp
@@ -119,7 +119,7 @@ bool SplashViewer::on_key(const KeyEvent key) {
void SplashViewer::paint(Painter& painter) {
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
- if (!portapack::display.drawBMP2({0, 0}, path_)) {
+ if (!portapack::display.draw_bmp_from_sdcard_file({0, 0}, path_)) {
painter.draw_string({10, 160}, *Theme::getInstance()->bg_darkest, "Not a valid splash image.");
return;
}
diff --git a/firmware/application/apps/ui_standalone_view.cpp b/firmware/application/apps/ui_standalone_view.cpp
index cbab847c..5fc1e229 100644
--- a/firmware/application/apps/ui_standalone_view.cpp
+++ b/firmware/application/apps/ui_standalone_view.cpp
@@ -22,6 +22,12 @@
#include "ui_standalone_view.hpp"
#include "irq_controls.hpp"
+#include "i2cdevmanager.hpp"
+#include "i2cdev_ppmod.hpp"
+
+#include "ui_font_fixed_5x8.hpp"
+#include "ui_font_fixed_8x16.hpp"
+
namespace ui {
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
@@ -34,6 +40,16 @@ void fill_rectangle(int x, int y, int width, int height, uint16_t color) {
painter.fill_rectangle({x, y, width, height}, ui::Color(color));
}
+void fill_rectangle_unrolled8(int x, int y, int width, int height, uint16_t color) {
+ ui::Painter painter;
+ painter.fill_rectangle_unrolled8({x, y, width, height}, ui::Color(color));
+}
+
+void draw_bitmap(int x, int y, int width, int height, const uint8_t* pixels, uint16_t foreground, uint16_t background) {
+ ui::Painter painter;
+ painter.draw_bitmap({x, y}, {{width, height}, pixels}, ui::Color(foreground), ui::Color(background));
+}
+
void* alloc(size_t size) {
void* p = chHeapAlloc(0x0, size);
if (p == nullptr)
@@ -45,6 +61,45 @@ uint64_t get_switches_state_ulong() {
return get_switches_state().to_ulong();
}
+ui::Coord scroll_area_y(const ui::Coord y) {
+ return portapack::display.scroll_area_y(y);
+}
+
+void scroll_set_area(const ui::Coord top_y, const ui::Coord bottom_y) {
+ portapack::display.scroll_set_area(top_y, bottom_y);
+}
+
+void scroll_disable() {
+ portapack::display.scroll_disable();
+}
+
+ui::Coord scroll_set_position(const ui::Coord position) {
+ return portapack::display.scroll_set_position(position);
+}
+
+ui::Coord scroll(const int32_t delta) {
+ return portapack::display.scroll(delta);
+}
+
+bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
+ auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
+ if (!dev) {
+ return false;
+ }
+
+ if (data_len == 0 || data == nullptr)
+ return dev->i2c_write(nullptr, 0, cmd, cmd_len);
+
+ return dev->i2c_read(cmd, cmd_len, data, data_len);
+}
+
+StandaloneView* standaloneView = nullptr;
+
+void set_dirty() {
+ if (standaloneView != nullptr)
+ standaloneView->set_dirty();
+}
+
standalone_application_api_t api = {
/* .malloc = */ &alloc,
/* .calloc = */ &calloc,
@@ -54,20 +109,78 @@ standalone_application_api_t api = {
/* .fill_rectangle = */ &fill_rectangle,
/* .swizzled_switches = */ &swizzled_switches,
/* .get_switches_state = */ &get_switches_state_ulong,
+ /* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
+ /* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
+ /* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
+ /* .draw_bitmap = */ &draw_bitmap,
+ /* .scroll_area_y = */ &scroll_area_y,
+ /* .scroll_set_area = */ &scroll_set_area,
+ /* .scroll_disable = */ &scroll_disable,
+ /* .scroll_set_position = */ &scroll_set_position,
+ /* .scroll = */ &scroll,
+ /* .i2c_read = */ &i2c_read,
+ /* .panic = */ &chDbgPanic,
+ /* .set_dirty = */ &set_dirty,
};
-StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr app_image)
- : nav_(nav), _app_image(std::move(app_image)) {
- get_application_information()->initialize(api);
- add_children({&dummy});
+StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
+ : nav_(nav),
+ _app_image(*app_image) {
+ if (app_image == nullptr) {
+ chDbgPanic("Invalid application image");
+ }
+
+ set_focusable(true);
+
+ standaloneView = this;
}
void StandaloneView::focus() {
- dummy.focus();
+ View::focus();
}
void StandaloneView::paint(Painter& painter) {
(void)painter;
+
+ if (initialized &&
+ get_application_information()->header_version > 1) {
+ get_application_information()->PaintViewMirror();
+ }
+}
+
+void StandaloneView::on_focus() {
+ if (get_application_information()->header_version > 1) {
+ get_application_information()->OnFocus();
+ }
+}
+
+bool StandaloneView::on_key(const KeyEvent key) {
+ if (get_application_information()->header_version > 1) {
+ return get_application_information()->OnKeyEvent((uint8_t)key);
+ }
+
+ return false;
+}
+
+bool StandaloneView::on_encoder(const EncoderEvent event) {
+ if (get_application_information()->header_version > 1) {
+ return get_application_information()->OnEncoder((int32_t)event);
+ }
+ return false;
+}
+
+bool StandaloneView::on_touch(const TouchEvent event) {
+ if (get_application_information()->header_version > 1) {
+ get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
+ }
+ return true;
+}
+
+bool StandaloneView::on_keyboard(const KeyboardEvent event) {
+ if (get_application_information()->header_version > 1) {
+ return get_application_information()->OnKeyboard((uint8_t)event);
+ }
+ return false;
}
void StandaloneView::frame_sync() {
@@ -79,4 +192,14 @@ void StandaloneView::frame_sync() {
}
}
+void StandaloneView::on_after_attach() {
+ context().focus_manager().setMirror(this);
+ get_application_information()->initialize(api);
+}
+
+void StandaloneView::on_before_detach() {
+ get_application_information()->shutdown();
+ context().focus_manager().clearMirror();
+}
+
} // namespace ui
diff --git a/firmware/application/apps/ui_standalone_view.hpp b/firmware/application/apps/ui_standalone_view.hpp
index 012d7f07..f5a8a78b 100644
--- a/firmware/application/apps/ui_standalone_view.hpp
+++ b/firmware/application/apps/ui_standalone_view.hpp
@@ -31,28 +31,34 @@ namespace ui {
class StandaloneView : public View {
public:
- StandaloneView(NavigationView& nav, std::unique_ptr app_image);
- virtual ~StandaloneView() override { get_application_information()->shutdown(); }
-
- void focus() override;
+ StandaloneView(NavigationView& nav, uint8_t* app_image);
+ virtual ~StandaloneView() override {
+ }
std::string title() const override { return (const char*)get_application_information()->app_name; };
+ void focus() override;
void paint(Painter& painter) override;
+
+ void on_focus() override;
+ bool on_key(const KeyEvent key) override;
+ bool on_encoder(const EncoderEvent event) override;
+ bool on_touch(const TouchEvent event) override;
+ bool on_keyboard(const KeyboardEvent event) override;
+
+ void on_after_attach() override;
+ void on_before_detach() override;
+
void frame_sync();
private:
bool initialized = false;
NavigationView& nav_;
- std::unique_ptr _app_image;
+ uint8_t& _app_image;
standalone_application_information_t* get_application_information() const {
- return reinterpret_cast(_app_image.get());
+ return reinterpret_cast(&_app_image);
}
- Button dummy{
- {240, 0, 0, 0},
- ""};
-
MessageHandlerRegistration message_handler_sample{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
diff --git a/firmware/application/apps/ui_subghzd.cpp b/firmware/application/apps/ui_subghzd.cpp
index bab43a7d..fa878252 100644
--- a/firmware/application/apps/ui_subghzd.cpp
+++ b/firmware/application/apps/ui_subghzd.cpp
@@ -24,6 +24,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
+#include "file_path.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -31,6 +32,18 @@ using namespace ui;
namespace ui {
+std::string SubGhzDRecentEntry::to_csv() {
+ std::string csv = ";";
+ csv += SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)sensorType);
+ csv += ";" + to_string_dec_uint(bits) + ";";
+ csv += to_string_hex(data, 64 / 4);
+ return csv;
+}
+
+void SubGhzDLogger::log_data(SubGhzDRecentEntry& data) {
+ log_file.write_entry(data.to_csv());
+}
+
void SubGhzDRecentEntryDetailView::update_data() {
// process protocol data
parseProtocol();
@@ -76,16 +89,25 @@ SubGhzDView::SubGhzDView(NavigationView& nav)
&field_vga,
&field_frequency,
&button_clear_list,
+ &check_log,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
+ logger = std::make_unique();
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(10000);
-
+ check_log.on_select = [this](Checkbox&, bool v) {
+ logging = v;
+ if (logger && logging) {
+ logger->append(logs_dir.string() + "/SUBGHZDLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
+ logger->write_header();
+ }
+ };
+ check_log.set_value(logging);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
@@ -107,6 +129,9 @@ void SubGhzDView::on_tick_second() {
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
SubGhzDRecentEntry key{data->sensorType, data->data, data->bits};
+ if (logger && logging) {
+ logger->log_data(key);
+ }
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
diff --git a/firmware/application/apps/ui_subghzd.hpp b/firmware/application/apps/ui_subghzd.hpp
index 20271a35..afdf79e2 100644
--- a/firmware/application/apps/ui_subghzd.hpp
+++ b/firmware/application/apps/ui_subghzd.hpp
@@ -34,6 +34,7 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
+#include "log_file.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/subghztypes.hpp"
@@ -68,6 +69,23 @@ struct SubGhzDRecentEntry {
void reset_age() {
age = 0;
}
+
+ std::string to_csv();
+};
+
+class SubGhzDLogger {
+ public:
+ Optional append(const std::filesystem::path& filename) {
+ return log_file.append(filename);
+ }
+
+ void log_data(SubGhzDRecentEntry& data);
+ void write_header() {
+ log_file.write_entry(";Type; Bits; Data;");
+ }
+
+ private:
+ LogFile log_file{};
};
using SubGhzDRecentEntries = RecentEntries;
using SubGhzDRecentEntriesView = RecentEntriesView;
@@ -93,10 +111,13 @@ class SubGhzDView : public View {
1'750'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
+ bool logging = false;
app_settings::SettingsManager settings_{
"rx_subghzd",
app_settings::Mode::RX,
- {}};
+ {
+ {"log"sv, &logging},
+ }};
SubGhzDRecentEntries recent{};
@@ -118,8 +139,16 @@ class SubGhzDView : public View {
{0, 16, 7 * 8, 32},
"Clear"};
+ Checkbox check_log{
+ {10 * 8, 18},
+ 3,
+ "Log",
+ true};
+
static constexpr auto header_height = 3 * 16;
+ std::unique_ptr logger{};
+
const RecentEntriesColumns columns{{
{"Type", 19},
{"Bits", 4},
diff --git a/firmware/application/apps/ui_tone_search.cpp b/firmware/application/apps/ui_tone_search.cpp
deleted file mode 100644
index e17ed52b..00000000
--- a/firmware/application/apps/ui_tone_search.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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_tone_search.hpp"
-
-#include "baseband_api.hpp"
-#include "string_format.hpp"
-
-using namespace portapack;
-
-namespace ui {
-
-void ToneSearchView::focus() {
- // field_frequency_min.focus();
-}
-
-ToneSearchView::~ToneSearchView() {
- receiver_model.disable();
- baseband::shutdown();
-}
-
-ToneSearchView::ToneSearchView(
- NavigationView& nav)
- : nav_(nav) {
- // baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
-
- add_children({
- &labels,
- &field_lna,
- &field_vga,
- &field_rf_amp,
- });
-}
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_tone_search.hpp b/firmware/application/apps/ui_tone_search.hpp
deleted file mode 100644
index 88499ef5..00000000
--- a/firmware/application/apps/ui_tone_search.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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 "receiver_model.hpp"
-
-#include "ui_receiver.hpp"
-
-namespace ui {
-
-class ToneSearchView : public View {
- public:
- ToneSearchView(NavigationView& nav);
- ~ToneSearchView();
-
- void focus() override;
-
- std::string title() const override { return "Tone search"; };
-
- private:
- NavigationView& nav_;
-
- Labels labels{
- {{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
-
- LNAGainField field_lna{
- {4 * 8, 0 * 16}};
-
- VGAGainField field_vga{
- {11 * 8, 0 * 16}};
-
- RFAmpField field_rf_amp{
- {18 * 8, 0 * 16}};
-
- /*
- MessageHandlerRegistration message_handler_frame_sync {
- Message::ID::DisplayFrameSync,
- [this](const Message* const) {
- if( this->fifo ) {
- ChannelSpectrum channel_spectrum;
- while( fifo->out(channel_spectrum) ) {
- this->on_channel_spectrum(channel_spectrum);
- }
- }
- this->do_timers();
- }
- };*/
-};
-
-} /* namespace ui */
diff --git a/firmware/application/apps/ui_weatherstation.cpp b/firmware/application/apps/ui_weatherstation.cpp
index bf63094c..2eae1607 100644
--- a/firmware/application/apps/ui_weatherstation.cpp
+++ b/firmware/application/apps/ui_weatherstation.cpp
@@ -25,6 +25,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
+#include "file_path.hpp"
#include "portapack_persistent_memory.hpp"
#include "../baseband/fprotos/fprotogeneral.hpp"
@@ -35,6 +36,21 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
+std::string WeatherRecentEntry::to_csv() {
+ std::string csv = ";";
+ csv += WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)sensorType);
+ csv += ";" + to_string_dec_uint(id) + ";";
+ csv += to_string_decimal(temp, 2) + ";";
+ csv += to_string_dec_uint(humidity) + ";";
+ csv += to_string_dec_uint(channel) + ";";
+ csv += to_string_dec_uint(battery_low);
+ return csv;
+}
+
+void WeatherLogger::log_data(WeatherRecentEntry& data) {
+ log_file.write_entry(data.to_csv());
+}
+
void WeatherRecentEntryDetailView::update_data() {
// set text elements
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
@@ -98,8 +114,11 @@ WeatherView::WeatherView(NavigationView& nav)
&field_frequency,
&options_temperature,
&button_clear_list,
+ &check_log,
&recent_entries_view});
+ logger = std::make_unique();
+
baseband::run_image(portapack::spi_flash::image_tag_weather);
button_clear_list.on_select = [this](Button&) {
@@ -114,6 +133,15 @@ WeatherView::WeatherView(NavigationView& nav)
};
options_temperature.set_selected_index(weather_units_fahr, false);
+ check_log.on_select = [this](Checkbox&, bool v) {
+ logging = v;
+ if (logger && logging) {
+ logger->append(logs_dir.string() + "/WEATHERLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
+ logger->write_header();
+ }
+ };
+ check_log.set_value(logging);
+
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
@@ -140,6 +168,9 @@ void WeatherView::on_tick_second() {
void WeatherView::on_data(const WeatherDataMessage* data) {
WeatherRecentEntry key = process_data(data);
+ if (logger && logging) {
+ logger->log_data(key);
+ }
// WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
@@ -213,6 +244,11 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
return "EmosE601x";
case FPW_SolightTE44:
return "SolightTE44";
+ case FPW_Bresser3CH:
+ case FPW_Bresser3CH_V1:
+ return "Bresser3CH";
+ case FPW_Vauno_EN8822:
+ return "Vauno EN8822";
case FPW_Invalid:
default:
return "Unknown";
@@ -532,6 +568,42 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
i16 |= 0xf000;
}
ret.temp = (float)i16 / 10.0;
+ break;
+ case FPW_Bresser3CH:
+ ret.id = (data->decode_data >> 28) & 0xff;
+ ret.channel = ((data->decode_data >> 27) & 0x01) | (((data->decode_data >> 26) & 0x01) << 1);
+ // ret.btn = ((data->decode_data >> 25) & 0x1);
+ ret.battery_low = ((data->decode_data >> 24) & 0x1);
+ i16 = (data->decode_data >> 12) & 0x0fff;
+ /* Handle signed data */
+ if (i16 & 0x0800) {
+ i16 |= 0xf000;
+ }
+ ret.temp = (float)i16 / 10.0;
+ ret.humidity = data->decode_data & 0xff;
+ break;
+ case FPW_Bresser3CH_V1:
+ ret.id = (data->decode_data >> 32) & 0xff;
+ ret.battery_low = ((data->decode_data >> 31) & 0x1);
+ // ret.btn = (data->decode_data >> 30) & 0x1;
+ ret.channel = (data->decode_data >> 28) & 0x3;
+ ret.temp = (data->decode_data >> 16) & 0xfff;
+ ret.temp = FProtoGeneral::locale_fahrenheit_to_celsius((float)(ret.temp - 900) / 10.0);
+ ret.humidity = (data->decode_data >> 8) & 0xff;
+ break;
+
+ case FPW_Vauno_EN8822:
+ ret.id = (data->decode_data >> 34) & 0xff;
+ ret.battery_low = (data->decode_data >> 33) & 0x01;
+ ret.channel = ((data->decode_data >> 30) & 0x03);
+ i16 = (data->decode_data >> 18) & 0x0fff;
+ /* Handle signed data */
+ if (i16 & 0x0800) {
+ i16 |= 0xf000;
+ }
+ ret.temp = (float)i16 / 10.0;
+ ret.humidity = (data->decode_data >> 11) & 0x7f;
+
break;
case FPW_Invalid:
default:
diff --git a/firmware/application/apps/ui_weatherstation.hpp b/firmware/application/apps/ui_weatherstation.hpp
index d1dfd563..6c265e51 100644
--- a/firmware/application/apps/ui_weatherstation.hpp
+++ b/firmware/application/apps/ui_weatherstation.hpp
@@ -30,6 +30,7 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
+#include "log_file.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/weathertypes.hpp"
@@ -78,7 +79,25 @@ struct WeatherRecentEntry {
void reset_age() {
age = 0;
}
+
+ std::string to_csv();
};
+
+class WeatherLogger {
+ public:
+ Optional append(const std::filesystem::path& filename) {
+ return log_file.append(filename);
+ }
+
+ void log_data(WeatherRecentEntry& data);
+ void write_header() {
+ log_file.write_entry(";Type; id; Temp; Hum; CH; Batt");
+ }
+
+ private:
+ LogFile log_file{};
+};
+
using WeatherRecentEntries = RecentEntries;
using WeatherRecentEntriesView = RecentEntriesView;
@@ -103,11 +122,13 @@ class WeatherView : public View {
1'750'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
+ bool logging = false;
app_settings::SettingsManager settings_{
"rx_weather",
app_settings::Mode::RX,
{
{"units_fahr"sv, &weather_units_fahr},
+ {"log"sv, &logging},
}};
WeatherRecentEntries recent{};
@@ -140,8 +161,16 @@ class WeatherView : public View {
{0, 16, 7 * 8, 32},
"Clear"};
+ Checkbox check_log{
+ {10 * 8, 18},
+ 3,
+ "Log",
+ true};
+
static constexpr auto header_height = 3 * 16;
+ std::unique_ptr logger{};
+
const RecentEntriesColumns columns{{
{"Type", 10},
{"Temp", 5},
diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp
index 37ef8690..704d727c 100644
--- a/firmware/application/bitmap.hpp
+++ b/firmware/application/bitmap.hpp
@@ -363,6 +363,44 @@ 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,
@@ -987,6 +1025,44 @@ static constexpr Bitmap bitmap_icon_tools_antenna{
{16, 16},
bitmap_icon_tools_antenna_data};
+static constexpr uint8_t bitmap_icon_batt_text_data[] = {
+ 0x00,
+ 0x00,
+ 0x30,
+ 0x06,
+ 0x48,
+ 0x09,
+ 0x48,
+ 0x09,
+ 0x70,
+ 0x0E,
+ 0x40,
+ 0x08,
+ 0x30,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x48,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0x10,
+ 0x00,
+ 0x48,
+ 0x00,
+ 0x00,
+ 0x00,
+};
+static constexpr Bitmap bitmap_icon_batt_text{
+ {16, 16},
+ bitmap_icon_batt_text_data};
+
static constexpr uint8_t bitmap_icon_new_file_data[] = {
0x00,
0x00,
@@ -1289,6 +1365,44 @@ 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,
@@ -1727,6 +1841,44 @@ 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,
@@ -2091,6 +2243,44 @@ 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,
@@ -2129,6 +2319,82 @@ 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,
+ 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,
@@ -2227,43 +2493,43 @@ static constexpr Bitmap bitmap_icon_file_image{
{16, 16},
bitmap_icon_file_image_data};
-static constexpr uint8_t bitmap_icon_temperature_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,
- 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{
+static constexpr Bitmap bitmap_icon_batt_icon{
{16, 16},
- bitmap_icon_temperature_data};
+ bitmap_icon_batt_icon_data};
static constexpr uint8_t bitmap_tab_edge_data[] = {
0x00,
@@ -2333,6 +2599,82 @@ 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,
+ 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,
@@ -2447,82 +2789,6 @@ static constexpr Bitmap bitmap_icon_camera{
{16, 16},
bitmap_icon_camera_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_icon_batt_text_data[] = {
- 0x00,
- 0x00,
- 0x30,
- 0x06,
- 0x48,
- 0x09,
- 0x48,
- 0x09,
- 0x70,
- 0x0E,
- 0x40,
- 0x08,
- 0x30,
- 0x06,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x00,
- 0x48,
- 0x00,
- 0x20,
- 0x00,
- 0x10,
- 0x00,
- 0x48,
- 0x00,
- 0x00,
- 0x00,
-};
-static constexpr Bitmap bitmap_icon_batt_text{
- {16, 16},
- bitmap_icon_batt_text_data};
-
static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = {
0xF0,
0x3F,
@@ -2637,6 +2903,44 @@ 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,
+ 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,
@@ -2903,6 +3207,44 @@ 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,
@@ -3113,6 +3455,44 @@ 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,
+ 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,
0x01,
@@ -3343,6 +3723,44 @@ 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,
@@ -3381,6 +3799,44 @@ 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,
@@ -3895,44 +4351,6 @@ static constexpr Bitmap bitmap_icon_looking{
{16, 16},
bitmap_icon_looking_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_delete_data[] = {
0x00,
0x00,
@@ -4575,6 +4993,44 @@ 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,
@@ -4747,6 +5203,44 @@ 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,
@@ -5417,234 +5911,6 @@ static constexpr Bitmap bitmap_target{
{16, 16},
bitmap_target_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_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_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_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_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_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_icon_speaker_and_headphones_mute_data[] = {
0x40,
0x00,
@@ -5683,233 +5949,81 @@ static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
{16, 16},
bitmap_icon_speaker_and_headphones_mute_data};
-static constexpr uint8_t bitmap_icon_shift_data[] = {
+static constexpr uint8_t bitmap_icon_new_dir_data[] = {
0x00,
0x00,
- 0x80,
+ 0x1E,
0x00,
- 0xC0,
+ 0x21,
+ 0x00,
+ 0xE1,
+ 0x7F,
0x01,
- 0xE0,
+ 0xC0,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0xF1,
+ 0x8F,
+ 0xF1,
+ 0x8F,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
0x03,
- 0xF0,
- 0x07,
+ 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,
- 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_trim_data[] = {
- 0x10,
- 0x10,
- 0x30,
+ 0x00,
+ 0x00,
+ 0xF3,
+ 0xE0,
+ 0x92,
+ 0xA0,
+ 0x9E,
+ 0xBF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0xF3,
+ 0x04,
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_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,
+ 0x1E,
};
-static constexpr Bitmap bitmap_icon_hide{
+static constexpr Bitmap bitmap_icon_protoview{
{16, 16},
- bitmap_icon_hide_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_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_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};
+ bitmap_icon_protoview_data};
} /* namespace ui */
diff --git a/firmware/application/emu_cc1101.cpp b/firmware/application/emu_cc1101.cpp
deleted file mode 100644
index de946e8b..00000000
--- a/firmware/application/emu_cc1101.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 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 "emu_cc1101.hpp"
-
-namespace cc1101 {
-
-void CC1101Emu::whitening_init() {
- whitening_pn = 0x1FF;
-}
-
-// See TI app note DN509
-uint8_t CC1101Emu::whiten_byte(uint8_t byte) {
- uint_fast8_t new_bit;
-
- byte ^= (whitening_pn & 0xFF);
-
- for (size_t step = 0; step < 8; step++) {
- new_bit = (whitening_pn & 1) ^ ((whitening_pn >> 5) & 1);
- whitening_pn >>= 1;
- whitening_pn |= (new_bit << 8);
- }
-
- return byte;
-}
-
-} /* namespace cc1101 */
diff --git a/firmware/application/apps/acars_app.cpp b/firmware/application/external/acars_rx/acars_app.cpp
similarity index 52%
rename from firmware/application/apps/acars_app.cpp
rename to firmware/application/external/acars_rx/acars_app.cpp
index a8a5e1f9..d7b5079d 100644
--- a/firmware/application/apps/acars_app.cpp
+++ b/firmware/application/external/acars_rx/acars_app.cpp
@@ -20,45 +20,26 @@
* Boston, MA 02110-1301, USA.
*/
-#include "acars_app.hpp"
-
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "file_path.hpp"
+#include "audio.hpp"
+#include "acars_app.hpp"
using namespace portapack;
-using namespace acars;
#include "string_format.hpp"
#include "utility.hpp"
-void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequency) {
- (void)frequency;
- std::string entry{}; //= "Raw: F:" + to_string_dec_uint(frequency) + "Hz ";
- entry.reserve(256);
+namespace ui::external_app::acars_rx {
- // Raw hex dump of all the bytes
- // for (size_t c = 0; c < packet.length(); c += 32)
- // entry += to_string_hex(packet.read(c, 32), 8) + " ";
-
- for (size_t c = 0; c < 256; c += 32)
- entry += to_string_bin(packet.read(c, 32), 32);
-
- log_file.write_entry(packet.received_at(), entry);
+void ACARSLogger::log_str(std::string msg) {
+ log_file.write_entry(msg);
}
-/*void ACARSLogger::log_decoded(
- const acars::Packet& packet,
- const std::string text) {
-
- log_file.write_entry(packet.timestamp(), text);
-}*/
-
-namespace ui {
-
ACARSAppView::ACARSAppView(NavigationView& nav)
: nav_{nav} {
- baseband::run_image(portapack::spi_flash::image_tag_acars);
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&rssi,
&channel,
@@ -66,6 +47,7 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
&field_lna,
&field_vga,
&field_frequency,
+ &field_volume,
&check_log,
&console});
@@ -79,6 +61,9 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
logger = std::make_unique();
if (logger)
logger->append(logs_dir / u"ACARS.TXT");
+
+ audio::set_rate(audio::Rate::Hz_24000);
+ audio::output::start();
}
ACARSAppView::~ACARSAppView() {
@@ -90,29 +75,31 @@ void ACARSAppView::focus() {
field_frequency.focus();
}
-void ACARSAppView::on_packet(const acars::Packet& packet) {
+void ACARSAppView::on_packet(const ACARSPacketMessage* packet) {
std::string console_info;
- /*if (!packet.is_valid()) {
- console_info = to_string_datetime(packet.received_at(), HMS);
- console_info += " INVALID";
-
- console.writeln(console_info);
- } else {
- console_info = to_string_datetime(packet.received_at(), HMS);
- console_info += ":" + to_string_bin(packet.read(0, 32), 32);
- //console_info += " REG:" + packet.registration_number();
-
- console.writeln(console_info);
- }*/
-
- packet_counter++;
- if (packet_counter % 10 == 0)
- console.writeln("Block #" + to_string_dec_uint(packet_counter));
-
- // Log raw data whatever it contains
- if (logger && logging)
- logger->log_raw_data(packet, receiver_model.target_frequency());
+ if (packet->state == 255) {
+ // got a packet, parse it, and display
+ rtc::RTC datetime;
+ rtc_time::now(datetime);
+ // todo parity error recovery
+ console_info = to_string_datetime(datetime, HMS);
+ console_info += ": ";
+ console_info += packet->message;
+ console.writeln(console_info);
+ // Log raw data whatever it contains
+ if (logger && logging)
+ logger->log_str(console_info);
+ } else {
+ // debug message arrived
+ console_info = "State: ";
+ console_info += to_string_dec_int(packet->state);
+ console_info += " lastbyte: ";
+ console_info += to_string_dec_uint(packet->message[0]);
+ console.writeln(console_info);
+ if (logger && logging)
+ logger->log_str(console_info);
+ }
}
-} /* namespace ui */
+} // namespace ui::external_app::acars_rx
\ No newline at end of file
diff --git a/firmware/application/apps/acars_app.hpp b/firmware/application/external/acars_rx/acars_app.hpp
similarity index 82%
rename from firmware/application/apps/acars_app.hpp
rename to firmware/application/external/acars_rx/acars_app.hpp
index fd688695..ac99e704 100644
--- a/firmware/application/apps/acars_app.hpp
+++ b/firmware/application/external/acars_rx/acars_app.hpp
@@ -31,23 +31,19 @@
#include "ui_rssi.hpp"
#include "log_file.hpp"
-#include "acars_packet.hpp"
+namespace ui::external_app::acars_rx {
class ACARSLogger {
public:
Optional append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
-
- void log_raw_data(const acars::Packet& packet, const uint32_t frequency);
- // void log_decoded(const acars::Packet& packet, const std::string text);
+ void log_str(std::string msg);
private:
LogFile log_file{};
};
-namespace ui {
-
class ACARSAppView : public View {
public:
ACARSAppView(NavigationView& nav);
@@ -55,12 +51,12 @@ class ACARSAppView : public View {
void focus() override;
- std::string title() const override { return "ACARS (WIP)"; };
+ std::string title() const override { return "ACARS"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{
- 131550000 /* frequency */,
+ 131825000 /* frequency */,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
@@ -85,7 +81,7 @@ class ACARSAppView : public View {
{0 * 8, 0 * 8},
nav_};
Checkbox check_log{
- {22 * 8, 21},
+ {16 * 8, 1 * 16},
3,
"LOG",
true};
@@ -93,19 +89,21 @@ class ACARSAppView : public View {
Console console{
{0, 3 * 16, 240, 256}};
+ AudioVolumeField field_volume{
+ {28 * 8, 1 * 16}};
+
std::unique_ptr logger{};
- void on_packet(const acars::Packet& packet);
+ void on_packet(const ACARSPacketMessage* packet);
MessageHandlerRegistration message_handler_packet{
Message::ID::ACARSPacket,
[this](Message* const p) {
const auto message = static_cast(p);
- const acars::Packet packet{message->packet};
- this->on_packet(packet);
+ this->on_packet(message);
}};
};
-} /* namespace ui */
+} // namespace ui::external_app::acars_rx
-#endif /*__ACARS_APP_H__*/
+#endif /*__ACARS_APP_H__*/
\ No newline at end of file
diff --git a/firmware/application/external/acars_rx/main.cpp b/firmware/application/external/acars_rx/main.cpp
new file mode 100644
index 00000000..d11adcb8
--- /dev/null
+++ b/firmware/application/external/acars_rx/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 "acars_app.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::acars_rx {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::acars_rx
+
+extern "C" {
+
+__attribute__((section(".external_app.app_acars_rx.application_information"), used)) application_information_t _application_information_acars_rx = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::acars_rx::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "ACARS",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/adsbtx/main.cpp b/firmware/application/external/adsbtx/main.cpp
index bb9e0ef9..9506211f 100644
--- a/firmware/application/external/adsbtx/main.cpp
+++ b/firmware/application/external/adsbtx/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_adsbtx.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_adsbtx */ {'P', 'A', 'D', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/afsk_rx/main.cpp b/firmware/application/external/afsk_rx/main.cpp
index aa2b3341..ca1a7cfc 100644
--- a/firmware/application/external/afsk_rx/main.cpp
+++ b/firmware/application/external/afsk_rx/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_afsk_rx.application_information"), use
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/analogtv/main.cpp b/firmware/application/external/analogtv/main.cpp
index 92ff6135..0106a091 100644
--- a/firmware/application/external/analogtv/main.cpp
+++ b/firmware/application/external/analogtv/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_analogtv.application_information"), us
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_am_tv */ {'P', 'A', 'M', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/audio_test/main.cpp b/firmware/application/external/audio_test/main.cpp
index 48554ce1..79003140 100644
--- a/firmware/application/external/audio_test/main.cpp
+++ b/firmware/application/external/audio_test/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_audio_test.application_information"),
},
/*.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 */ {'P', 'A', 'B', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/audio_test/ui_audio_test.cpp b/firmware/application/external/audio_test/ui_audio_test.cpp
index de138764..8b6de1de 100644
--- a/firmware/application/external/audio_test/ui_audio_test.cpp
+++ b/firmware/application/external/audio_test/ui_audio_test.cpp
@@ -26,7 +26,7 @@
using namespace portapack;
-namespace ui {
+namespace ui::external_app::audio_test {
AudioTestView::AudioTestView(NavigationView& nav)
: nav_{nav} {
@@ -103,4 +103,4 @@ void AudioTestView::update_audio_beep() {
baseband::request_beep_stop();
}
-} /* namespace ui */
+} /* namespace ui::external_app::audio_test */
diff --git a/firmware/application/external/audio_test/ui_audio_test.hpp b/firmware/application/external/audio_test/ui_audio_test.hpp
index 1d6119b5..65003303 100644
--- a/firmware/application/external/audio_test/ui_audio_test.hpp
+++ b/firmware/application/external/audio_test/ui_audio_test.hpp
@@ -25,7 +25,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
-namespace ui {
+namespace ui::external_app::audio_test {
class AudioTestView : public View {
public:
@@ -96,6 +96,6 @@ class AudioTestView : public View {
Theme::getInstance()->bg_dark->background};
};
-} /* namespace ui */
+} /* namespace ui::external_app::audio_test */
#endif /*__UI_AUDIO_TEST_H__*/
diff --git a/firmware/application/external/blespam/main.cpp b/firmware/application/external/blespam/main.cpp
index aa8e8b2a..d58beb99 100644
--- a/firmware/application/external/blespam/main.cpp
+++ b/firmware/application/external/blespam/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_blespam.application_information"), use
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/calculator/main.cpp b/firmware/application/external/calculator/main.cpp
index e03488b6..ebb2c985 100644
--- a/firmware/application/external/calculator/main.cpp
+++ b/firmware/application/external/calculator/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_calculator.application_information"),
},
/*.icon_color = */ ui::Color::yellow().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/external/coasterp/main.cpp b/firmware/application/external/coasterp/main.cpp
index a81d5888..0415f842 100644
--- a/firmware/application/external/coasterp/main.cpp
+++ b/firmware/application/external/coasterp/main.cpp
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_coasterp.application_information"), us
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/cvs_spam/cvs_spam.cpp b/firmware/application/external/cvs_spam/cvs_spam.cpp
new file mode 100644
index 00000000..3486d719
--- /dev/null
+++ b/firmware/application/external/cvs_spam/cvs_spam.cpp
@@ -0,0 +1,310 @@
+// 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 "cvs_spam.hpp"
+#include "io_file.hpp"
+#include "metadata_file.hpp"
+#include "oversample.hpp"
+#include "io_convert.hpp"
+#include "lfsr_random.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::cvs_spam {
+
+void CVSSpamView::file_error(const std::filesystem::path& path, const std::string& error_details) {
+ nav_.display_modal("Error",
+ "File error:\n" + path.string() + "\n" + error_details);
+}
+
+void CVSSpamView::refresh_list() {
+ file_list.clear();
+ current_page = page;
+ uint32_t count = 0;
+
+ for (const auto& entry : std::filesystem::directory_iterator(cvsfiles_dir, u"*")) {
+ if (std::filesystem::is_regular_file(entry.status())) {
+ auto filename = entry.path().filename().string();
+ auto extension = entry.path().extension().string();
+ std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
+
+ if (extension == ".c16" || extension == ".cu8") {
+ if (count >= (page - 1) * 50 && count < page * 50) {
+ file_list.push_back(entry.path());
+ if (file_list.size() == 50) {
+ page++;
+ break;
+ }
+ }
+ count++;
+ }
+ }
+ }
+
+ if (file_list.empty()) {
+ if (page == 1) {
+ menu_view.hidden(true);
+ text_empty.hidden(false);
+ } else {
+ page = 1;
+ refresh_list();
+ return;
+ }
+ } else {
+ menu_view.hidden(false);
+ text_empty.hidden(true);
+ menu_view.clear();
+ for (const auto& file : file_list) {
+ menu_view.add_item({file.filename().string(),
+ ui::Theme::getInstance()->fg_green->foreground,
+ nullptr,
+ [this](KeyEvent key) {
+ if (key == KeyEvent::Select) {
+ start_tx(menu_view.highlighted_index());
+ }
+ }});
+ }
+ page_info.set("Page " + std::to_string(current_page));
+ menu_view.set_highlighted(0);
+ }
+
+ if (file_list.size() < 50) {
+ page = 1;
+ }
+}
+
+void CVSSpamView::start_tx(const uint32_t id) {
+ if (is_active()) {
+ stop_tx();
+ return;
+ }
+
+ const uint32_t sample_rate = 250000;
+
+ current_file = cvsfiles_dir / file_list[id].filename();
+
+ File capture_file;
+ auto open_error = capture_file.open(current_file);
+ if (open_error) {
+ file_error(current_file,
+ "Cannot open file.\n"
+ "Initial file check failed.\n"
+ "Path: " +
+ cvsfiles_dir.string() +
+ "\n"
+ "Error: " +
+ std::to_string(static_cast(open_error)));
+ 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));
+
+ auto reader = std::make_unique();
+ if (auto error = reader->open(current_file)) {
+ file_error(current_file,
+ "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) +
+ "\n"
+ "Error: " +
+ std::to_string(static_cast(error)));
+ return;
+ }
+
+ 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.enable();
+
+ chThdSleepMilliseconds(100);
+
+ replay_thread = std::make_unique(
+ std::move(reader),
+ read_size,
+ buffer_count,
+ &ready_signal,
+ [](uint32_t return_code) {
+ ReplayThreadDoneMessage message{return_code};
+ EventDispatcher::send_message(message);
+ });
+}
+
+void CVSSpamView::start_random_tx() {
+ if (is_active()) {
+ stop_tx();
+ return;
+ }
+
+ if (file_list.empty()) {
+ nav_.display_modal("Error", "No files found!");
+ return;
+ }
+
+ 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;
+ auto open_error = capture_file.open(current_file);
+ if (open_error) {
+ file_error(current_file, "Cannot open file.\nInitial check failed.");
+ 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));
+
+ auto reader = std::make_unique();
+ if (auto error = reader->open(current_file)) {
+ file_error(current_file, "Cannot read file data.");
+ return;
+ }
+
+ 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.enable();
+
+ chThdSleepMilliseconds(100);
+
+ replay_thread = std::make_unique(
+ std::move(reader),
+ read_size,
+ buffer_count,
+ &ready_signal,
+ [](uint32_t return_code) {
+ ReplayThreadDoneMessage message{return_code};
+ EventDispatcher::send_message(message);
+ });
+}
+
+void CVSSpamView::on_tx_progress(const uint32_t progress) {
+ if (is_active()) {
+ progressbar.set_value(progress % 100);
+ progressbar.set_style(Theme::getInstance()->fg_red);
+ }
+}
+
+void CVSSpamView::focus() {
+ menu_view.focus();
+}
+
+bool CVSSpamView::is_active() const {
+ return (bool)replay_thread;
+}
+
+void CVSSpamView::stop_tx() {
+ replay_thread.reset();
+ transmitter_model.disable();
+ ready_signal = false;
+ thread_sync_complete = false;
+ chaos_mode = false;
+ progressbar.set_value(0);
+ chThdSleepMilliseconds(50);
+}
+
+CVSSpamView::CVSSpamView(NavigationView& nav)
+ : nav_{nav},
+ current_file{} {
+ baseband::run_image(portapack::spi_flash::image_tag_replay);
+
+ add_children({&menu_view,
+ &text_empty,
+ &button_prev_page,
+ &button_next_page,
+ &page_info,
+ &button_send,
+ &button_chaos,
+ &button_stop,
+ &progressbar});
+
+ button_send.set_style(Theme::getInstance()->fg_magenta);
+ button_chaos.set_style(Theme::getInstance()->fg_yellow);
+ button_stop.set_style(Theme::getInstance()->fg_red);
+
+ transmitter_model.set_sampling_rate(1536000);
+ transmitter_model.set_baseband_bandwidth(1750000);
+ transmitter_model.set_rf_amp(true);
+ transmitter_model.set_tx_gain(47);
+ transmitter_model.set_channel_bandwidth(1750000);
+
+ refresh_list();
+
+ button_prev_page.on_select = [this](Button&) {
+ if (is_active()) return;
+ if (current_page == 1) return;
+ if (current_page == 2) page = 1;
+ page = current_page - 1;
+ refresh_list();
+ };
+
+ button_next_page.on_select = [this](Button&) {
+ if (is_active()) return;
+ refresh_list();
+ };
+
+ button_send.on_select = [this](Button&) {
+ if (!file_list.empty()) {
+ start_tx(menu_view.highlighted_index());
+ }
+ };
+
+ button_chaos.on_select = [this](Button&) {
+ if (is_active()) {
+ chaos_mode = false;
+ stop_tx();
+ } else {
+ chaos_mode = true;
+ start_random_tx();
+ }
+ };
+
+ button_stop.on_select = [this](Button&) {
+ if (is_active()) {
+ stop_tx();
+ }
+ };
+}
+
+CVSSpamView::~CVSSpamView() {
+ stop_tx();
+ baseband::shutdown();
+}
+
+} // namespace ui::external_app::cvs_spam
\ No newline at end of file
diff --git a/firmware/application/external/cvs_spam/cvs_spam.hpp b/firmware/application/external/cvs_spam/cvs_spam.hpp
new file mode 100644
index 00000000..77320d4c
--- /dev/null
+++ b/firmware/application/external/cvs_spam/cvs_spam.hpp
@@ -0,0 +1,136 @@
+// 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"
+#include "ui_transmitter.hpp"
+#include "replay_thread.hpp"
+#include "baseband_api.hpp"
+#include "ui_language.hpp"
+#include "file_path.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::cvs_spam {
+
+class CVSSpamView : public View {
+ public:
+ explicit CVSSpamView(NavigationView& nav);
+ ~CVSSpamView();
+
+ void focus() override;
+ std::string title() const override { return "CVS Spam"; };
+
+ private:
+ static constexpr size_t read_size = 0x4000;
+ static constexpr size_t buffer_count = 3;
+
+ lfsr_word_t lfsr_v = 1;
+ bool chaos_mode{false};
+
+ NavigationView& nav_;
+ std::unique_ptr replay_thread{};
+ bool ready_signal{false};
+ bool thread_sync_complete{false};
+ std::filesystem::path current_file;
+
+ bool is_active() const;
+ void stop_tx();
+ void file_error(const std::filesystem::path& path, const std::string& error_details);
+ void refresh_list();
+ void start_tx(const uint32_t id);
+ void start_random_tx();
+ void on_tx_progress(const uint32_t progress);
+
+ uint32_t page = 1;
+ uint32_t current_page = 1;
+ std::vector file_list{};
+
+ MenuView menu_view{
+ {0, 0, 240, 180},
+ true};
+
+ Text text_empty{
+ {7 * 8, 12 * 8, 16 * 8, 16},
+ "Empty directory!"};
+
+ Button button_prev_page{
+ {0, 180, 50, 32},
+ "<"};
+
+ Button button_next_page{
+ {190, 180, 50, 32},
+ ">"};
+
+ Text page_info{
+ {95, 188, 50, 16}};
+
+ Button button_send{
+ {0, 212, 75, 36},
+ "CALL"};
+
+ Button button_chaos{
+ {82, 212, 75, 36},
+ "CHAOS"};
+
+ Button button_stop{
+ {165, 212, 75, 36},
+ LanguageHelper::currentMessages[LANG_STOP]};
+
+ ProgressBar progressbar{
+ {0, 256, 240, 44}};
+
+ MessageHandlerRegistration message_handler_fifo_signal{
+ Message::ID::RequestSignal,
+ [this](const Message* const p) {
+ const auto message = static_cast(p);
+ if (message->signal == RequestSignalMessage::Signal::FillRequest) {
+ ready_signal = true;
+ }
+ }};
+
+ MessageHandlerRegistration message_handler_tx_progress{
+ Message::ID::TXProgress,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ this->on_tx_progress(message.progress);
+ }};
+
+ MessageHandlerRegistration message_handler_replay_thread_done{
+ Message::ID::ReplayThreadDone,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ if (message.return_code == ReplayThread::END_OF_FILE) {
+ if (chaos_mode) {
+ replay_thread.reset();
+ transmitter_model.disable();
+ ready_signal = false;
+ lfsr_v = lfsr_iterate(lfsr_v);
+ size_t random_index = lfsr_v % file_list.size();
+ menu_view.set_highlighted(random_index);
+ chThdSleepMilliseconds(100);
+ start_tx(random_index);
+ } else {
+ thread_sync_complete = true;
+ stop_tx();
+ }
+ } else if (message.return_code == ReplayThread::READ_ERROR) {
+ file_error(file_list[menu_view.highlighted_index()], "Read error during playback");
+ if (chaos_mode) {
+ replay_thread.reset();
+ transmitter_model.disable();
+ ready_signal = false;
+ lfsr_v = lfsr_iterate(lfsr_v);
+ size_t random_index = lfsr_v % file_list.size();
+ menu_view.set_highlighted(random_index);
+ chThdSleepMilliseconds(100);
+ start_tx(random_index);
+ }
+ }
+ }};
+};
+
+} // namespace ui::external_app::cvs_spam
\ No newline at end of file
diff --git a/firmware/application/external/cvs_spam/main.cpp b/firmware/application/external/cvs_spam/main.cpp
new file mode 100644
index 00000000..b9ccb5f0
--- /dev/null
+++ b/firmware/application/external/cvs_spam/main.cpp
@@ -0,0 +1,34 @@
+// 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 "ui.hpp"
+#include "cvs_spam.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::cvs_spam {
+void initialize_app(NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::cvs_spam
+
+extern "C" {
+
+__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::cvs_spam::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "CVS Spam",
+ /*.bitmap_data = */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
+ /*.icon_color = */ ui::Color::red().v,
+ /*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'R', 'E', 'P'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
\ No newline at end of file
diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake
index 8bc794ec..03fbffad 100644
--- a/firmware/application/external/external.cmake
+++ b/firmware/application/external/external.cmake
@@ -96,6 +96,56 @@ set(EXTCPPSRC
#sstvtx
external/sstvtx/main.cpp
external/sstvtx/ui_sstvtx.cpp
+
+ #random
+ external/random_password/main.cpp
+ external/random_password/ui_random_password.cpp
+ external/random_password/sha512.cpp
+ external/random_password/sha512.h
+
+ #acars
+ external/acars_rx/main.cpp
+ external/acars_rx/acars_app.cpp
+
+ #shoppingcart_lock
+ external/shoppingcart_lock/main.cpp
+ external/shoppingcart_lock/shoppingcart_lock.cpp
+
+ #ookbrute
+ external/ookbrute/main.cpp
+ external/ookbrute/ui_ookbrute.cpp
+
+ #ook_editor
+ external/ook_editor/main.cpp
+ external/ook_editor/ui_ook_editor.cpp
+
+ #cvs_spam
+ external/cvs_spam/main.cpp
+ external/cvs_spam/cvs_spam.cpp
+
+ #flippertx
+ external/flippertx/main.cpp
+ external/flippertx/ui_flippertx.cpp
+
+ #remote
+ external/remote/main.cpp
+ external/remote/ui_remote.cpp
+
+ #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
)
set(EXTAPPLIST
@@ -117,9 +167,21 @@ set(EXTAPPLIST
foxhunt_rx
audio_test
wardrivemap
+ cvs_spam
tpmsrx
protoview
adsbtx
morse_tx
sstvtx
+ random_password
+ #acars_rx
+ ookbrute
+ ook_editor
+ shoppingcart_lock
+ flippertx
+ remote
+ mcu_temperature
+ fmradio
+ tuner
+ metronome
)
diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld
index 932c76a6..51a24ea5 100644
--- a/firmware/application/external/external.ld
+++ b/firmware/application/external/external.ld
@@ -46,6 +46,18 @@ MEMORY
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
}
SECTIONS
@@ -164,7 +176,6 @@ SECTIONS
*(*ui*external_app*tpmsrx*);
} > ram_external_app_tpmsrx
-
.external_app_protoview : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_protoview.application_information));
@@ -190,6 +201,75 @@ SECTIONS
*(*ui*external_app*sstvtx*);
} > ram_external_app_sstvtx
+ .external_app_random_password : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_random_password.application_information));
+ *(*ui*external_app*random_password*);
+ } > ram_external_app_random_password
+ .external_app_acars_rx : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_acars_rx.application_information));
+ *(*ui*external_app*acars_rx*);
+ } > ram_external_app_acars_rx
+ .external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_shoppingcart_lock.application_information));
+ *(*ui*external_app*shoppingcart_lock*);
+ } > ram_external_app_shoppingcart_lock
+
+ .external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_cvs_spam.application_information));
+ *(*ui*external_app*cvs_spam*);
+ } > ram_external_app_cvs_spam
+
+ .external_app_ookbrute : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_ookbrute.application_information));
+ *(*ui*external_app*ookbrute*);
+ } > ram_external_app_ookbrute
+
+ .external_app_ook_editor : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_ook_editor.application_information));
+ *(*ui*external_app*ook_editor*);
+ } > ram_external_app_ook_editor
+
+ .external_app_flippertx : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_flippertx.application_information));
+ *(*ui*external_app*flippertx*);
+ } > ram_external_app_flippertx
+
+ .external_app_remote : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_remote.application_information));
+ *(*ui*external_app*remote*);
+ } > ram_external_app_remote
+
+ .external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
+ {
+ 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));
+ *(*ui*external_app*tuner*);
+ } > ram_external_app_tuner
+
+ .external_app_metronome : ALIGN(4) SUBALIGN(4)
+ {
+ KEEP(*(.external_app.app_metronome.application_information));
+ *(*ui*external_app*metronome*);
+ } > ram_external_app_metronome
}
diff --git a/firmware/application/external/extsensors/main.cpp b/firmware/application/external/extsensors/main.cpp
index 27926409..4597dd19 100644
--- a/firmware/application/external/extsensors/main.cpp
+++ b/firmware/application/external/extsensors/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_extsensors.application_information"),
},
/*.icon_color = */ ui::Color::orange().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/extsensors/ui_extsensors.cpp b/firmware/application/external/extsensors/ui_extsensors.cpp
index 572effe6..d36a5a93 100644
--- a/firmware/application/external/extsensors/ui_extsensors.cpp
+++ b/firmware/application/external/extsensors/ui_extsensors.cpp
@@ -22,6 +22,7 @@
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
+#include "i2cdevmanager.hpp"
using namespace portapack;
using namespace ui;
@@ -29,7 +30,7 @@ using namespace ui;
namespace ui::external_app::extsensors {
void ExtSensorsView::focus() {
- text_info.focus();
+ console.focus();
}
ExtSensorsView::ExtSensorsView(NavigationView& nav)
@@ -39,10 +40,33 @@ ExtSensorsView::ExtSensorsView(NavigationView& nav)
&text_gps,
&text_orientation,
&text_envl1,
- &text_envl2});
+ &text_envl2,
+ &text_envl3,
+ &console});
+
+ prev_scan_int = i2cdev::I2CDevManager::get_autoscan_interval();
+ refreshi2c();
+ i2cdev::I2CDevManager::set_autoscan_interval(3); // scan each 3 sec for new i2c devices
+}
+
+void ExtSensorsView::on_new_dev() {
+ refreshi2c();
+}
+
+void ExtSensorsView::refreshi2c() {
+ console.clear(true);
+ console.writeln("Found I2C devices:");
+ auto addrlist = i2cdev::I2CDevManager::get_gev_list_by_addr();
+ for (size_t i = 0; i < addrlist.size(); ++i) {
+ console.write("0x");
+ console.write(to_string_hex(addrlist[i]));
+ console.write(", ");
+ }
+ if (addrlist.size() == 0) console.writeln("No I2C devs found.");
}
ExtSensorsView::~ExtSensorsView() {
+ i2cdev::I2CDevManager::set_autoscan_interval(prev_scan_int);
}
void ExtSensorsView::on_any() {
@@ -78,9 +102,14 @@ void ExtSensorsView::on_environment(const EnvironmentDataMessage* msg) {
tmp += "C";
tmp += "; H: " + to_string_decimal(msg->humidity, 1) + "%"; // humidity
text_envl1.set(tmp);
- tmp = "P: " + to_string_decimal(msg->pressure, 1) + " hPa; L:"; // pressure
- tmp += to_string_dec_int(msg->light) + " LUX"; // light
+ tmp = "P: " + to_string_decimal(msg->pressure, 1) + " hPa"; // pressure
text_envl2.set(tmp);
}
+void ExtSensorsView::on_light(const LightDataMessage* msg) {
+ on_any();
+ std::string tmp = "L: " + to_string_dec_int(msg->light) + " LUX";
+ text_envl3.set(tmp);
+}
+
} // namespace ui::external_app::extsensors
\ No newline at end of file
diff --git a/firmware/application/external/extsensors/ui_extsensors.hpp b/firmware/application/external/extsensors/ui_extsensors.hpp
index e0472361..555e576b 100644
--- a/firmware/application/external/extsensors/ui_extsensors.hpp
+++ b/firmware/application/external/extsensors/ui_extsensors.hpp
@@ -53,6 +53,7 @@ class ExtSensorsView : public View {
NavigationView& nav_;
bool has_data = false;
+ uint16_t prev_scan_int = 0;
Labels labels{
{{0 * 8, 3 * 16}, "GPS:", Theme::getInstance()->fg_light->foreground},
@@ -64,9 +65,15 @@ class ExtSensorsView : public View {
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
Text text_envl2{{1 * 8, 9 * 16, 24 * 8, 16}, "-"};
+ Text text_envl3{{1 * 8, 11 * 16, 24 * 8, 16}, "-"};
+ Console console{
+ {1, 13 * 16, screen_width - 1, screen_height - 13 * 16}};
+ void refreshi2c();
+ void on_new_dev();
void on_any();
+ void on_light(const LightDataMessage* msg);
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
void on_environment(const EnvironmentDataMessage* msg);
@@ -90,6 +97,20 @@ class ExtSensorsView : public View {
const auto message = static_cast(p);
this->on_environment(message);
}};
+
+ MessageHandlerRegistration message_handler_light{
+ Message::ID::LightData,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ this->on_light(message);
+ }};
+
+ MessageHandlerRegistration message_handler_dev{
+ Message::ID::I2CDevListChanged,
+ [this](Message* const p) {
+ (void)p; // make compiler happy
+ this->on_new_dev();
+ }};
};
}; // namespace ui::external_app::extsensors
diff --git a/firmware/application/external/flippertx/main.cpp b/firmware/application/external/flippertx/main.cpp
new file mode 100644
index 00000000..3dde72c2
--- /dev/null
+++ b/firmware/application/external/flippertx/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_flippertx.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::flippertx {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::flippertx
+
+extern "C" {
+
+__attribute__((section(".external_app.app_flippertx.application_information"), used)) application_information_t _application_information_flippertx = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::flippertx::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "FlipperTx",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_ookstreaming */ {'P', 'O', 'S', 'K'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/flippertx/ui_flippertx.cpp b/firmware/application/external/flippertx/ui_flippertx.cpp
new file mode 100644
index 00000000..03572ed0
--- /dev/null
+++ b/firmware/application/external/flippertx/ui_flippertx.cpp
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 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_flippertx.hpp"
+#include "audio.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+#include "buffer_exchange.hpp"
+
+using namespace portapack;
+using namespace ui;
+
+namespace ui::external_app::flippertx {
+
+void FlipperTxView::focus() {
+ button_browse.focus();
+}
+
+void FlipperTxView::set_ready() {
+ ready_signal = true;
+}
+
+FlipperTxView::FlipperTxView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&button_startstop,
+ &field_frequency,
+ &tx_view,
+ &field_filename,
+ &button_browse});
+
+ button_startstop.on_select = [this](Button&) {
+ if (is_running) {
+ stop();
+ } else {
+ start();
+ }
+ };
+
+ button_browse.on_select = [this](Button&) {
+ auto open_view = nav_.push(".sub");
+ open_view->push_dir(subghz_dir);
+ open_view->on_changed = [this](std::filesystem::path new_file_path) {
+ if (on_file_changed(new_file_path)) {
+ ; // tif (button_startstop.parent()) button_startstop.focus(); parent_ is null error.....
+ }
+ };
+ };
+}
+
+bool FlipperTxView::on_file_changed(std::filesystem::path new_file_path) {
+ submeta = read_flippersub_file(new_file_path);
+ filename = "";
+ if (!submeta.is_valid()) {
+ field_filename.set_text("File: err, bad file");
+ return false;
+ }
+ proto = submeta.value().protocol;
+ if (proto != FLIPPER_PROTO_RAW && proto != FLIPPER_PROTO_BINRAW) { // temp disabled
+ field_filename.set_text("File: err, not supp. proto");
+ return false;
+ }
+ preset = submeta.value().preset;
+ if (preset != FLIPPER_PRESET_OOK) {
+ field_filename.set_text("File: err, not supp. preset");
+ return false;
+ }
+ te = submeta.value().te;
+ binraw_bit_count = submeta.value().binraw_bit_count;
+ /*if (binraw_bit_count >= 512 * 800) {
+ field_filename.set_text("File: err, too long"); // should stream, but not in this scope YET
+ return false;
+ }*/
+ field_filename.set_text("File: " + new_file_path.string());
+ filename = new_file_path;
+ return true;
+}
+
+void FlipperTxView::stop() {
+ if (is_running && replay_thread) replay_thread.reset();
+ is_running = false;
+ ready_signal = false;
+
+ transmitter_model.disable();
+ baseband::shutdown();
+ button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
+}
+
+bool FlipperTxView::start() {
+ if (filename.empty()) return false;
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+ transmitter_model.enable();
+ button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
+ // start thread
+ replay_thread = std::make_unique(
+ filename,
+ 1024, 4,
+ &ready_signal,
+ submeta.value().protocol,
+ submeta.value().te,
+ [](uint32_t return_code) {
+ ReplayThreadDoneMessage message{return_code};
+ EventDispatcher::send_message(message);
+ });
+ is_running = true;
+ return true;
+}
+
+void FlipperTxView::on_tx_progress(const bool done) {
+ if (done) {
+ if (is_running) {
+ stop();
+ }
+ }
+}
+
+FlipperTxView::~FlipperTxView() {
+ stop();
+}
+
+FlipperPlayThread::FlipperPlayThread(
+ std::filesystem::path filename,
+ size_t read_size,
+ size_t buffer_count,
+ bool* ready_signal,
+ FlipperProto proto,
+ uint16_t te,
+ std::function terminate_callback)
+ : config{read_size, buffer_count},
+ filename{filename},
+ ready_sig{ready_signal},
+ proto{proto},
+ te{te},
+ terminate_callback{std::move(terminate_callback)} {
+ thread = chThdCreateFromHeap(NULL, 6 * 1024, NORMALPRIO + 10, FlipperPlayThread::static_fn, this);
+}
+
+FlipperPlayThread::~FlipperPlayThread() {
+ if (thread) {
+ chThdTerminate(thread);
+ if (thread->p_refs) chThdWait(thread);
+ thread = nullptr;
+ }
+}
+
+msg_t FlipperPlayThread::static_fn(void* arg) {
+ auto obj = static_cast(arg);
+ const auto return_code = obj->run();
+ if (obj->terminate_callback) {
+ obj->terminate_callback(return_code);
+ }
+ chThdExit(0);
+ return 0;
+}
+
+uint32_t FlipperPlayThread::run() {
+ BasebandReplay replay{&config};
+ BufferExchange buffers{&config};
+ StreamBuffer* prefill_buffer{nullptr};
+ int32_t read_result = 0;
+ // assume it is ok, since prev we checked it.
+ // open the sub file
+ File f; // don't worry, destructor will close it.
+ auto error = f.open(filename);
+ if (error) return READ_ERROR;
+ // seek to the first data
+ if (proto == FLIPPER_PROTO_RAW)
+ seek_flipper_raw_first_data(f);
+ else {
+ seek_flipper_binraw_first_data(f);
+ }
+ // Wait for FIFOs to be allocated in baseband
+ // Wait for _view to tell us that the buffers are ready
+ while (!(*ready_sig) && !chThdShouldTerminate()) {
+ chThdSleep(100);
+ };
+ uint8_t readall = 0;
+ int32_t endsignals[3] = {0, 42069, 613379}; // unlikely a valid data
+ // While empty buffers fifo is not empty...
+ while (!buffers.empty() && !chThdShouldTerminate()) {
+ prefill_buffer = buffers.get_prefill();
+
+ if (prefill_buffer == nullptr) {
+ buffers.put_app(prefill_buffer);
+ } else {
+ size_t c = 0;
+ for (c = 0; c < config.read_size / 4;) {
+ read_result = 0;
+ if (0 == readall) {
+ if (proto == FLIPPER_PROTO_RAW) {
+ auto data = read_flipper_raw_next_data(f);
+ if (!data.is_valid()) {
+ ((int32_t*)prefill_buffer->data())[c] = endsignals[++readall];
+ } else {
+ read_result = data.value();
+ ((int32_t*)prefill_buffer->data())[c] = read_result;
+ }
+ c++;
+
+ } else { // BINRAW
+ auto data = read_flipper_binraw_next_data(f);
+ if (!data.is_valid())
+ readall = 1;
+ else {
+ read_result = data.value();
+ // add 8 data
+ for (uint8_t bit = 0; bit < 8; bit++) {
+ ((int32_t*)prefill_buffer->data())[c + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
+ c++;
+ }
+ }
+ }
+ } else {
+ ((int32_t*)prefill_buffer->data())[c] = endsignals[readall];
+ if (readall == 1) readall++;
+ c++;
+ }
+ }
+ prefill_buffer->set_size(config.read_size);
+ buffers.put(prefill_buffer);
+ }
+ };
+ baseband::set_fifo_data(nullptr);
+ if (readall) return END_OF_FILE;
+
+ while (!chThdShouldTerminate()) {
+ auto buffer = buffers.get();
+ int32_t read_result = 0;
+ for (size_t d = 0; d < buffer->capacity() / 4; d++) {
+ read_result = -133469;
+ if (!readall) {
+ if (proto == FLIPPER_PROTO_RAW) {
+ auto data = read_flipper_raw_next_data(f);
+ if (!data.is_valid()) {
+ readall = 1;
+ } else {
+ read_result = data.value();
+ }
+ } else {
+ auto data = read_flipper_binraw_next_data(f);
+ if (!data.is_valid())
+ readall = 1;
+ else {
+ read_result = data.value();
+ // add 8 data
+ for (uint8_t bit = 0; bit < 8; bit++) {
+ ((int32_t*)prefill_buffer->data())[d + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
+ }
+ d += 7;
+ continue;
+ }
+ }
+ }
+ if (readall >= 1 && readall <= 2) {
+ read_result = endsignals[readall++];
+ }
+ ((int32_t*)buffer->data())[d] = read_result;
+ }
+ buffer->set_size(buffer->capacity());
+ buffers.put(buffer);
+ if (readall == 3) return END_OF_FILE;
+ }
+ return TERMINATED;
+}
+
+} // namespace ui::external_app::flippertx
diff --git a/firmware/application/external/flippertx/ui_flippertx.hpp b/firmware/application/external/flippertx/ui_flippertx.hpp
new file mode 100644
index 00000000..a1e05877
--- /dev/null
+++ b/firmware/application/external/flippertx/ui_flippertx.hpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 HTotoo
+ *
+ * This file is part of PortaPack.
+ *
+ */
+
+#ifndef __UI_flippertx_H__
+#define __UI_flippertx_H__
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_navigation.hpp"
+#include "ui_transmitter.hpp"
+#include "ui_freq_field.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "utility.hpp"
+#include "string_format.hpp"
+#include "file_path.hpp"
+#include "metadata_file.hpp"
+#include "flipper_subfile.hpp"
+#include "ui_fileman.hpp"
+#include "baseband_api.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::flippertx {
+
+#define OOK_SAMPLERATE 2280000U
+class FlipperPlayThread;
+class FlipperTxView : public View {
+ public:
+ FlipperTxView(NavigationView& nav);
+ ~FlipperTxView();
+
+ void focus() override;
+
+ std::string title() const override {
+ return "FlipperTx";
+ };
+
+ private:
+ NavigationView& nav_;
+ TxRadioState radio_state_{
+ 433920000 /* frequency */,
+ 1750000 /* bandwidth */,
+ OOK_SAMPLERATE /* sampling rate */
+ };
+
+ TxFrequencyField field_frequency{
+ {0 * 8, 0 * 16},
+ nav_};
+ TransmitterView2 tx_view{
+ {11 * 8, 0 * 16},
+ /*short_ui*/ true};
+
+ app_settings::SettingsManager settings_{
+ "tx_flippertx", app_settings::Mode::TX};
+
+ TextField field_filename{
+ {0, 2 * 16, 300, 1 * 8},
+ "File: -"};
+
+ Button button_startstop{
+ {1, 6 * 16, 96, 24},
+ LanguageHelper::currentMessages[LANG_START]};
+
+ Button button_browse{
+ {1, 3 * 16 + 3, 96, 24},
+ LanguageHelper::currentMessages[LANG_BROWSE]};
+
+ bool is_running{false};
+
+ bool start();
+ void stop();
+
+ void set_ready();
+ void on_tx_progress(const bool done);
+ bool on_file_changed(std::filesystem::path new_file_path);
+
+ std::filesystem::path filename = {};
+ FlipperProto proto = FLIPPER_PROTO_UNSUPPORTED;
+ FlipperPreset preset = FLIPPER_PRESET_UNK;
+ uint16_t te = 0; // for binraw
+ uint32_t binraw_bit_count = 0;
+ bool ready_signal = false;
+
+ 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) {
+ const auto message = *reinterpret_cast(p);
+ this->on_tx_progress(message.done);
+ }};
+
+ MessageHandlerRegistration message_handler_fifo_signal{
+ Message::ID::RequestSignal,
+ [this](const Message* const p) {
+ const auto message = static_cast(p);
+ if (message->signal == RequestSignalMessage::Signal::FillRequest) {
+ this->set_ready();
+ }
+ }};
+
+ MessageHandlerRegistration message_handler_replay_thread_done{
+ Message::ID::ReplayThreadDone,
+ [this](const Message* const p) {
+ // const auto message = *reinterpret_cast(p);
+ (void)p;
+ // stop(); //don't stop for now, stop when i get the tx finished msg
+ }};
+};
+
+struct BasebandReplay {
+ BasebandReplay(ReplayConfig* const config) {
+ baseband::replay_start(config);
+ }
+
+ ~BasebandReplay() {
+ // baseband::replay_stop(); // wont, since we need to send out that is in the buffer
+ }
+};
+
+class FlipperPlayThread {
+ public:
+ FlipperPlayThread(
+ std::filesystem::path,
+ size_t read_size,
+ size_t buffer_count,
+ bool* ready_signal,
+ FlipperProto proto,
+ uint16_t te,
+ std::function terminate_callback);
+ ~FlipperPlayThread();
+
+ FlipperPlayThread(const FlipperPlayThread&) = delete;
+ FlipperPlayThread(FlipperPlayThread&&) = delete;
+ FlipperPlayThread& operator=(const FlipperPlayThread&) = delete;
+ FlipperPlayThread& operator=(FlipperPlayThread&&) = delete;
+
+ const ReplayConfig& state() const {
+ return config;
+ };
+
+ enum FlipperPlayThread_return {
+ READ_ERROR = 0,
+ END_OF_FILE,
+ TERMINATED
+ };
+
+ private:
+ ReplayConfig config;
+ std::filesystem::path filename;
+ bool* ready_sig;
+ FlipperProto proto;
+ uint16_t te;
+ std::function terminate_callback;
+ Thread* thread{nullptr};
+
+ static msg_t static_fn(void* arg);
+
+ uint32_t run();
+};
+}; // namespace ui::external_app::flippertx
+
+#endif /*__UI_flippertx_H__*/
diff --git a/firmware/application/external/fmradio/main.cpp b/firmware/application/external/fmradio/main.cpp
new file mode 100644
index 00000000..040fbe8c
--- /dev/null
+++ b/firmware/application/external/fmradio/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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_fmradio.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::fmradio {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::fmradio
+
+extern "C" {
+
+__attribute__((section(".external_app.app_fmradio.application_information"), used)) application_information_t _application_information_fmradio = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::fmradio::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "FM Radio",
+ /*.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,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_wfm_audio */ {'P', 'W', 'F', 'M'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/fmradio/ui_fmradio.cpp b/firmware/application/external/fmradio/ui_fmradio.cpp
new file mode 100644
index 00000000..ece1d211
--- /dev/null
+++ b/firmware/application/external/fmradio/ui_fmradio.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 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_fmradio.hpp"
+
+#include "audio.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+
+using namespace portapack;
+using namespace modems;
+using namespace ui;
+
+namespace ui::external_app::fmradio {
+
+void FmRadioView::focus() {
+ field_frequency.focus();
+}
+
+FmRadioView::FmRadioView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
+
+ add_children({&rssi,
+ &field_rf_amp,
+ &field_lna,
+ &field_vga,
+ &field_volume,
+ &field_frequency,
+ &btn_fav_save,
+ &txt_save_help,
+ &btn_fav_0,
+ &btn_fav_1,
+ &btn_fav_2,
+ &btn_fav_3,
+ &btn_fav_4,
+ &btn_fav_5,
+ &btn_fav_6,
+ &btn_fav_7,
+ &btn_fav_8,
+ &btn_fav_9,
+ &audio,
+ &waveform});
+
+ 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 (field_frequency.value() == 0) {
+ field_frequency.set_value(87000000);
+ }
+
+ receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
+
+ field_frequency.set_step(25000);
+ receiver_model.enable();
+ audio::output::start();
+
+ 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;
+ txt_save_help.set_text(save_fav ? "Select slot" : "");
+ txt_save_help.visible(save_fav);
+ txt_save_help.set_dirty();
+ };
+
+ update_fav_btn_texts();
+}
+
+void FmRadioView::on_btn_clicked(uint8_t i) {
+ if (save_fav) {
+ save_fav = false;
+ freq_fav_list[i] = field_frequency.value();
+ 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]);
+}
+
+std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
+ std::string nice = to_string_dec_uint(freq / 1000000);
+ nice += ".";
+ nice += to_string_dec_uint((freq / 10000) % 100);
+ return nice;
+}
+
+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]));
+}
+
+FmRadioView::~FmRadioView() {
+ receiver_model.disable();
+ baseband::shutdown();
+ audio::output::stop();
+}
+
+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();
+}
+
+} // namespace ui::external_app::fmradio
diff --git a/firmware/application/external/fmradio/ui_fmradio.hpp b/firmware/application/external/fmradio/ui_fmradio.hpp
new file mode 100644
index 00000000..74fc65ea
--- /dev/null
+++ b/firmware/application/external/fmradio/ui_fmradio.hpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+/*
+ FUTURE TODO: implement search function.
+*/
+
+#ifndef __UI_fmradio_H__
+#define __UI_fmradio_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_spectrum.hpp"
+#include "ui_record_view.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "log_file.hpp"
+#include "utility.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::fmradio {
+
+#define FMR_BTNGRID_TOP 60
+
+class FmRadioView : public View {
+ public:
+ FmRadioView(NavigationView& nav);
+ FmRadioView& operator=(const FmRadioView&) = delete;
+ FmRadioView(const FmRadioView&) = delete;
+ ~FmRadioView();
+
+ void focus() override;
+
+ std::string title() const override { return "FM radio"; };
+
+ private:
+ NavigationView& nav_;
+ RxRadioState radio_state_{};
+ int16_t audio_spectrum[128]{0};
+ bool audio_spectrum_update = false;
+ AudioSpectrum* audio_spectrum_data{nullptr};
+ rf::Frequency freq_fav_list[12] = {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]}}};
+
+ 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{
+ {28 * 8, 0 * 16}};
+
+ RxFrequencyField field_frequency{
+ {0 * 8, 0 * 16},
+ nav_};
+
+ TextField txt_save_help{
+ {2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
+ " "};
+
+ Audio audio{
+ {21 * 8, 10, 6 * 8, 4}};
+
+ Waveform waveform{
+ {0, 20, 30 * 8, 2 * 16},
+ audio_spectrum,
+ 128,
+ 0,
+ false,
+ Theme::getInstance()->bg_darkest->foreground};
+
+ 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}, "---"};
+ Button btn_fav_2{{2, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_3{{2 + 15 * 8, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_4{{2, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_5{{2 + 15 * 8, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_6{{2, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_7{{2 + 15 * 8, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_8{{2, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
+ Button btn_fav_9{{2 + 15 * 8, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
+
+ 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();
+
+ MessageHandlerRegistration message_handler_audio_spectrum{
+ Message::ID::AudioSpectrum,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ audio_spectrum_data = message.data;
+ audio_spectrum_update = true;
+ }};
+ MessageHandlerRegistration message_handler_frame_sync{
+ Message::ID::DisplayFrameSync,
+ [this](const Message* const) {
+ if (audio_spectrum_update) {
+ audio_spectrum_update = false;
+ on_audio_spectrum();
+ }
+ }};
+};
+
+} // namespace ui::external_app::fmradio
+
+#endif /*__UI_fmradio_H__*/
diff --git a/firmware/application/external/font_viewer/main.cpp b/firmware/application/external/font_viewer/main.cpp
index be07615c..7110a3d2 100644
--- a/firmware/application/external/font_viewer/main.cpp
+++ b/firmware/application/external/font_viewer/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_font_viewer.application_information"),
},
/*.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/foxhunt/main.cpp b/firmware/application/external/foxhunt/main.cpp
index 1f652846..8aa239c0 100644
--- a/firmware/application/external/foxhunt/main.cpp
+++ b/firmware/application/external/foxhunt/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_foxhunt_rx.application_information"),
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_am_audio */ {'P', 'A', 'M', 'A'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/gpssim/main.cpp b/firmware/application/external/gpssim/main.cpp
index ebcfdd07..763bdbb7 100644
--- a/firmware/application/external/gpssim/main.cpp
+++ b/firmware/application/external/gpssim/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_gpssim.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_gpssim */ {'P', 'G', 'P', 'S'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/jammer/main.cpp b/firmware/application/external/jammer/main.cpp
index 8fe3403b..7c01ba0e 100644
--- a/firmware/application/external/jammer/main.cpp
+++ b/firmware/application/external/jammer/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_jammer.application_information"), used
},
/*.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/keyfob/main.cpp b/firmware/application/external/keyfob/main.cpp
index 0c40554a..e12b2194 100644
--- a/firmware/application/external/keyfob/main.cpp
+++ b/firmware/application/external/keyfob/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_keyfob.application_information"), used
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/lcr/main.cpp b/firmware/application/external/lcr/main.cpp
index aa748af7..1d2b86e6 100644
--- a/firmware/application/external/lcr/main.cpp
+++ b/firmware/application/external/lcr/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lcr.application_information"), used))
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/lge/main.cpp b/firmware/application/external/lge/main.cpp
index e09564e8..d2794a0f 100644
--- a/firmware/application/external/lge/main.cpp
+++ b/firmware/application/external/lge/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lge.application_information"), used))
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/mcu_temperature/main.cpp b/firmware/application/external/mcu_temperature/main.cpp
new file mode 100644
index 00000000..64b1ba13
--- /dev/null
+++ b/firmware/application/external/mcu_temperature/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 Bernd Herzog
+ *
+ * This file is part of PortaPack.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "ui.hpp"
+#include "mcu_temperature.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::mcu_temperature {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::mcu_temperature
+
+extern "C" {
+
+__attribute__((section(".external_app.app_mcu_temperature.application_information"), used)) application_information_t _application_information_mcu_temperature = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::mcu_temperature::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Temperature",
+ /*.bitmap_data = */ {
+ 0x00,
+ 0x00,
+ 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,
+ },
+ /*.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
+};
+}
\ No newline at end of file
diff --git a/firmware/application/external/mcu_temperature/mcu_temperature.cpp b/firmware/application/external/mcu_temperature/mcu_temperature.cpp
new file mode 100644
index 00000000..aee90308
--- /dev/null
+++ b/firmware/application/external/mcu_temperature/mcu_temperature.cpp
@@ -0,0 +1,91 @@
+#include "mcu_temperature.hpp"
+
+#include "ui_painter.hpp"
+
+#include "portapack.hpp"
+
+using namespace portapack;
+namespace ui::external_app::mcu_temperature {
+
+void McuTemperatureWidget::paint(Painter& painter) {
+ const auto logger = portapack::temperature_logger;
+
+ const auto rect = screen_rect();
+ const Color color_background{0, 0, 64};
+ const Color color_foreground = Theme::getInstance()->fg_green->foreground;
+ const Color color_reticle{128, 128, 128};
+
+ const auto graph_width = static_cast(logger.capacity()) * bar_width;
+ const Rect graph_rect{
+ rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
+ graph_width, rect.height()};
+ const Rect frame_rect{
+ graph_rect.left() - 1, graph_rect.top() - 1,
+ graph_rect.width() + 2, graph_rect.height() + 2};
+ painter.draw_rectangle(frame_rect, color_reticle);
+ painter.fill_rectangle(graph_rect, color_background);
+
+ const auto history = logger.history();
+ for (size_t i = 0; i < history.size(); i++) {
+ const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
+ const auto sample = history[i];
+ const auto temp = temperature(sample);
+ const auto y = screen_y(temp, graph_rect);
+ const Dim bar_height = graph_rect.bottom() - y;
+ painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
+ }
+
+ if (!history.empty()) {
+ const auto sample = history.back();
+ const auto temp = temperature(sample);
+ const auto last_y = screen_y(temp, graph_rect);
+ const Coord x = graph_rect.right() + 8;
+ const Coord y = last_y - 8;
+
+ painter.draw_string({x, y}, style(), temperature_str(temp));
+ }
+
+ const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
+ for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
+ const int32_t tick_length = 6;
+ const auto tick_x = graph_rect.left() - tick_length;
+ const auto tick_y = screen_y(temp, graph_rect);
+ painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
+ const auto text_x = graph_rect.left() - temp_len * 8 - 8;
+ const auto text_y = tick_y - 8;
+ painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
+ }
+}
+
+McuTemperatureWidget::temperature_t McuTemperatureWidget::temperature(const sample_t sensor_value) const {
+ // Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
+ return sensor_value;
+}
+
+std::string McuTemperatureWidget::temperature_str(const temperature_t temperature) const {
+ return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
+}
+
+Coord McuTemperatureWidget::screen_y(
+ const temperature_t temperature,
+ const Rect& rect) const {
+ int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
+ const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
+ return y_limit;
+}
+
+McuTemperatureView::McuTemperatureView(NavigationView& nav) {
+ add_children({
+ &text_title,
+ &temperature_widget,
+ &button_done,
+ });
+
+ button_done.on_select = [&nav](Button&) { nav.pop(); };
+}
+
+void McuTemperatureView::focus() {
+ button_done.focus();
+}
+
+} // namespace ui::external_app::mcu_temperature
\ No newline at end of file
diff --git a/firmware/application/external/mcu_temperature/mcu_temperature.hpp b/firmware/application/external/mcu_temperature/mcu_temperature.hpp
new file mode 100644
index 00000000..a8cbb617
--- /dev/null
+++ b/firmware/application/external/mcu_temperature/mcu_temperature.hpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, 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 __MCU_TEMPERATURE_H__
+#define __MCU_TEMPERATURE_H__
+
+#include "ui.hpp"
+#include "ui_widget.hpp"
+#include "ui_painter.hpp"
+#include "ui_menu.hpp"
+#include "ui_navigation.hpp"
+
+#include "rffc507x.hpp"
+#include "portapack.hpp"
+#include "memory_map.hpp"
+#include "irq_controls.hpp"
+
+#include
+#include
+
+namespace ui::external_app::mcu_temperature {
+
+class McuTemperatureWidget : public Widget {
+ public:
+ explicit McuTemperatureWidget(
+ Rect parent_rect)
+ : Widget{parent_rect} {
+ }
+
+ void paint(Painter& painter) override;
+
+ private:
+ using sample_t = uint32_t;
+ using temperature_t = int32_t;
+
+ temperature_t temperature(const sample_t sensor_value) const;
+ Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
+
+ std::string temperature_str(const temperature_t temperature) const;
+
+ static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
+ static constexpr temperature_t display_temp_scale = 3;
+ static constexpr int bar_width = 1;
+ static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
+};
+
+class McuTemperatureView : public View {
+ public:
+ explicit McuTemperatureView(NavigationView& nav);
+
+ void focus() override;
+
+ std::string title() const override { return "Temperature"; };
+
+ private:
+ Text text_title{
+ {76, 16, 240, 16},
+ "Temperature",
+ };
+
+ McuTemperatureWidget temperature_widget{
+ {0, 40, 240, 180},
+ };
+
+ Button button_done{
+ {72, 264, 96, 24},
+ "Done"};
+};
+
+} // namespace ui::external_app::mcu_temperature
+
+#endif /*__MCU_TEMPERATURE_H__*/
diff --git a/firmware/application/external/metronome/main.cpp b/firmware/application/external/metronome/main.cpp
new file mode 100644
index 00000000..620e8962
--- /dev/null
+++ b/firmware/application/external/metronome/main.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 Bernd
+ *
+ * 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_metronome.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::metronome {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::metronome
+
+extern "C" {
+
+__attribute__((section(".external_app.app_metronome.application_information"), used)) application_information_t _application_information_metronome = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::metronome::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Metronome",
+ /*.bitmap_data = */ {
+ 0x00,
+ 0x00,
+ 0xC0,
+ 0x43,
+ 0x20,
+ 0x66,
+ 0xA0,
+ 0x34,
+ 0x20,
+ 0x18,
+ 0xB0,
+ 0x0C,
+ 0x10,
+ 0x04,
+ 0x10,
+ 0x06,
+ 0x10,
+ 0x0B,
+ 0x98,
+ 0x19,
+ 0x08,
+ 0x10,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF8,
+ 0x1F,
+ 0xF0,
+ 0x0F,
+ 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 */ {'P', 'A', 'B', 'P'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
\ No newline at end of file
diff --git a/firmware/application/external/metronome/ui_metronome.cpp b/firmware/application/external/metronome/ui_metronome.cpp
new file mode 100644
index 00000000..75dca3b3
--- /dev/null
+++ b/firmware/application/external/metronome/ui_metronome.cpp
@@ -0,0 +1,177 @@
+/*
+ * copyleft 2024 sommermorgentraum
+ *
+ * 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_metronome.hpp"
+#include "baseband_api.hpp"
+#include "audio.hpp"
+#include "portapack.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::metronome {
+
+MetronomeView::MetronomeView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
+
+ add_children({
+ &labels,
+ &field_volume,
+ &button_play_stop,
+ &field_rythm_unaccent_time,
+ &field_rythm_accent_time,
+ &field_accent_beep_tune,
+ &field_unaccent_beep_tune,
+ &field_beep_flash_duration,
+ &field_bpm,
+ &progressbar,
+ });
+
+ field_bpm.set_value(120);
+ field_rythm_accent_time.set_value(4);
+ field_rythm_unaccent_time.set_value(4);
+ field_accent_beep_tune.set_value(880);
+ field_unaccent_beep_tune.set_value(440);
+ field_beep_flash_duration.set_value(100);
+ button_play_stop.on_select = [this]() {
+ if (playing_) {
+ stop_play();
+ } else {
+ play();
+ }
+ };
+
+ field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
+ field_volume.set_value(99);
+
+ audio::set_rate(audio::Rate::Hz_48000);
+
+ audio::output::start();
+}
+
+MetronomeView::~MetronomeView() {
+ should_exit = true;
+ if (thread) {
+ chThdWait(thread);
+ thread = nullptr;
+ }
+ receiver_model.disable();
+ baseband::shutdown();
+ audio::output::stop();
+}
+
+void MetronomeView::focus() {
+ field_bpm.focus();
+}
+
+void MetronomeView::stop_play() {
+ if (playing_) {
+ playing_ = false;
+ button_play_stop.set_bitmap(&bitmap_icon_replay);
+ baseband::request_beep_stop();
+ progressbar.set_value(0);
+ progressbar.set_style(Theme::getInstance()->fg_light);
+ }
+}
+
+void MetronomeView::play() {
+ if (!playing_) {
+ playing_ = true;
+ current_beat_ = 0;
+ button_play_stop.set_bitmap(&bitmap_icon_sleep);
+
+ if (!thread) {
+ thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, MetronomeView::static_fn, this);
+ }
+ }
+}
+
+void MetronomeView::beep_accent_beat() {
+ baseband::request_audio_beep(field_accent_beep_tune.value(), 48000, field_beep_flash_duration.value());
+}
+
+void MetronomeView::beep_unaccent_beat() {
+ baseband::request_audio_beep(field_unaccent_beep_tune.value(), 48000, field_beep_flash_duration.value());
+}
+
+// TODO: draw the beat
+// void MetronomeView::paint(Painter& painter) {
+// View::paint(painter);
+
+// painter.fill_rectangle(
+// {visual_x, visual_y, visual_width, visual_height},
+// Theme::getInstance()->bg_darkest->background);
+
+// if (playing_) {
+// const bool is_accent_beat = (current_beat_ % field_rythm_accent_time.value()) == 0;
+
+// const Color beat_color = is_accent_beat ?
+// Theme::getInstance()->fg_red->foreground :
+// Theme::getInstance()->fg_green->foreground;
+
+// painter.fill_rectangle(
+// {visual_x + visual_width/4,
+// visual_y + visual_height/4,
+// visual_width/2,
+// visual_height/2},
+// beat_color);
+// }
+// }
+
+msg_t MetronomeView::static_fn(void* arg) {
+ auto obj = static_cast(arg);
+ obj->run();
+ return 0;
+}
+
+void MetronomeView::run() {
+ while (!should_exit) {
+ if (!playing_) {
+ chThdSleepMilliseconds(100);
+ continue;
+ }
+
+ uint32_t base_interval = (60 * 1000) / field_bpm.value(); // quarter note as 1 beat
+
+ uint32_t beats_per_measure = field_rythm_unaccent_time.value(); // how many beates per bar
+ progressbar.set_max(beats_per_measure);
+ uint32_t beat_unit = field_rythm_accent_time.value(); // which note type (quarter, eighth, etc.) as 1 beat
+
+ uint32_t actual_interval = (base_interval * 4) / beat_unit; // e.g. when beat_unit==8 it's 1/2 of base_interval AKA eighths notes
+
+ uint32_t beat_in_measure = current_beat_ % beats_per_measure; // current beat in this bar (need to decide accent or unaccent)
+ progressbar.set_value(beat_in_measure + 1);
+
+ // accent beat is the first beat of this bar
+ if (beat_in_measure == 0) {
+ beep_accent_beat();
+ progressbar.set_style(Theme::getInstance()->fg_red);
+ } else {
+ beep_unaccent_beat();
+ progressbar.set_style(Theme::getInstance()->fg_green);
+ }
+
+ current_beat_++;
+ chThdSleepMilliseconds(actual_interval);
+ }
+}
+
+} // 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
new file mode 100644
index 00000000..d813146d
--- /dev/null
+++ b/firmware/application/external/metronome/ui_metronome.hpp
@@ -0,0 +1,126 @@
+/*
+ * copyleft 2024 sommermorgentraum
+ *
+ * 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_METRONOME_H__
+#define __UI_METRONOME_H__
+
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "audio.hpp"
+#include "ch.h"
+
+namespace ui::external_app::metronome {
+
+class MetronomeView : public View {
+ public:
+ MetronomeView(NavigationView& nav);
+ ~MetronomeView();
+ MetronomeView(const MetronomeView& other) = delete;
+ MetronomeView& operator=(const MetronomeView& other) = delete;
+
+ void focus() override;
+
+ std::string title() const override { return "Metronome"; };
+
+ private:
+ NavigationView& nav_;
+
+ void beep_accent_beat(); // e.g. 3 of 3/4 beat
+ void beep_unaccent_beat(); // e.g. 4 of 3/4 beat
+ void stop_play();
+ void play();
+ // void paint(Painter& painter) override;
+
+ Thread* thread{nullptr};
+ bool should_exit{false};
+ static msg_t static_fn(void* arg);
+ void run();
+
+ bool playing_{false};
+ uint32_t current_beat_{0};
+
+ Labels labels{
+ {{0 * 8, 1 * 16}, "BPM:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 2 * 16}, "Accent Beep Tune:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 3 * 16}, "Unaccent Beep Tune:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 4 * 16}, "Rhythm:", Theme::getInstance()->fg_light->foreground},
+ {{(sizeof("Rhythm:") + 1) * 8 + 4 * 8, 4 * 16}, "/", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 5 * 16}, "Beep Flash Duration:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 6 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
+
+ NumberField field_bpm{
+ {(sizeof("BPM:") + 1) * 8, 1 * 16},
+ 4,
+ {1, 1000},
+ 1,
+ ' '};
+
+ NumberField field_rythm_unaccent_time{// e.g. 3 in 3/4 beat
+ {(sizeof("Rhythm:") + 1) * 8, 4 * 16},
+ 2,
+ {1, 99},
+ 1,
+ ' '};
+
+ NumberField field_rythm_accent_time{// e.g. 4 in 3/4 beat
+ {(sizeof("Rhythm:") + 1) * 8 + 5 * 8, 4 * 16},
+ 2,
+ {1, 99},
+ 1,
+ ' '};
+
+ NumberField field_beep_flash_duration{
+ {(sizeof("Beep Flash Duration:") + 1) * 8, 5 * 16},
+ 3,
+ {10, 999},
+ 1,
+ ' '};
+
+ NumberField field_accent_beep_tune{
+ {(sizeof("Accent Beep Tune:") + 1) * 8, 2 * 16},
+ 5,
+ {380, 24000},
+ 20,
+ ' '};
+
+ NumberField field_unaccent_beep_tune{
+ {(sizeof("Unaccent Beep Tune:") + 1) * 8, 3 * 16},
+ 5,
+ {380, 24000},
+ 20,
+ ' '};
+
+ AudioVolumeField field_volume{
+ {(sizeof("Volume:") + 1) * 8, 6 * 16}};
+
+ NewButton button_play_stop{
+ {0 * 16, 16 * 16, screen_width, screen_height - 16 * 16},
+ {},
+ &bitmap_icon_replay,
+ Theme::getInstance()->fg_red->foreground};
+
+ ProgressBar progressbar{
+ {0 * 16, 8 * 16, screen_width, screen_height - 14 * 16}};
+};
+
+} // namespace ui::external_app::metronome
+
+#endif /*__UI_METRONOME_H__*/
\ No newline at end of file
diff --git a/firmware/application/external/morse_tx/main.cpp b/firmware/application/external/morse_tx/main.cpp
index a1d499a1..b591599f 100644
--- a/firmware/application/external/morse_tx/main.cpp
+++ b/firmware/application/external/morse_tx/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_morse_tx.application_information"), us
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_tones */ {'P', 'T', 'O', 'N'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/morse_tx/ui_morse.cpp b/firmware/application/external/morse_tx/ui_morse.cpp
index 99af99bd..e6421ab0 100644
--- a/firmware/application/external/morse_tx/ui_morse.cpp
+++ b/firmware/application/external/morse_tx/ui_morse.cpp
@@ -232,9 +232,8 @@ MorseView::MorseView(
set_foxhunt(foxhunt_code);
};
- options_modulation.on_change = [this](size_t i, int32_t value) {
- (void)i; // avoid unused warning
- mode_cw = (bool)value;
+ options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
+ mode_cw = (bool)v;
};
options_loop.on_change = [this](size_t i, uint32_t value) {
diff --git a/firmware/application/external/morse_tx/ui_morse.hpp b/firmware/application/external/morse_tx/ui_morse.hpp
index d3783af0..57da2cad 100644
--- a/firmware/application/external/morse_tx/ui_morse.hpp
+++ b/firmware/application/external/morse_tx/ui_morse.hpp
@@ -41,6 +41,11 @@ using namespace morse;
namespace ui::external_app::morse_tx {
+enum Modulation {
+ FM = 0,
+ CW = 1
+};
+
class MorseView : public View {
public:
MorseView(NavigationView& nav);
@@ -137,8 +142,8 @@ class MorseView : public View {
OptionsField options_modulation{
{15 * 8, 10 * 8},
2,
- {{"CW", true},
- {"FM", false}}};
+ {{"CW", Modulation::CW},
+ {"FM", Modulation::FM}}};
OptionsField options_loop{
{9 * 8, 12 * 8},
diff --git a/firmware/application/external/nrf_rx/main.cpp b/firmware/application/external/nrf_rx/main.cpp
index 3a3f1369..f7a4912b 100644
--- a/firmware/application/external/nrf_rx/main.cpp
+++ b/firmware/application/external/nrf_rx/main.cpp
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_nrf_rx.application_information"), used
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_nrf_rx */ {'P', 'N', 'R', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/ook_editor/main.cpp b/firmware/application/external/ook_editor/main.cpp
new file mode 100644
index 00000000..39692a4a
--- /dev/null
+++ b/firmware/application/external/ook_editor/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
+ *
+ * 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_ook_editor.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::ook_editor {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::ook_editor
+
+extern "C" {
+
+__attribute__((section(".external_app.app_ook_editor.application_information"), used)) application_information_t _application_information_ook_editor = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::ook_editor::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "OOKEditor",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/ook_editor/ui_ook_editor.cpp b/firmware/application/external/ook_editor/ui_ook_editor.cpp
new file mode 100644
index 00000000..a4e53ccc
--- /dev/null
+++ b/firmware/application/external/ook_editor/ui_ook_editor.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
+ *
+ * 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_ook_editor.hpp"
+
+using namespace portapack;
+using namespace ui;
+
+namespace fs = std::filesystem;
+
+namespace ui::external_app::ook_editor {
+
+// give focus to set button
+void OOKEditorAppView::focus() {
+ button_set.focus();
+}
+
+// update internal ook_data with GUI values
+void OOKEditorAppView::update_ook_data_from_app() {
+ ook_data.frequency = field_frequency.value();
+ ook_data.sample_rate = field_sample_rate.selected_index_value();
+ ook_data.symbol_rate = field_symbol_rate.value();
+ ook_data.repeat = field_repeat.value();
+ ook_data.pause_symbol_duration = field_pause_symbol_duration.value();
+}
+
+// `start_tx` method: Configures and begins OOK data transmission with a specific message.
+void OOKEditorAppView::start_tx() {
+ // check if there is a payload
+ if (ook_data.payload.length() < 1) {
+ text_app_status.set("Error: no payload to tx !!");
+ return;
+ }
+ progressbar.set_max(field_repeat.value()); // Size the progress bar accordingly to the number of repeat
+ is_transmitting = true; // set transmitting flag
+ button_send_stop.set_text(LanguageHelper::currentMessages[LANG_STOP]); // set button back to initial "start" state
+ start_ook_file_tx(ook_data); // start the transmission
+}
+
+// `stop_tx` method: Stops the transmission and resets the progress bar.
+void OOKEditorAppView::stop_tx() {
+ // TODO: model stopped but message still spamming.
+ is_transmitting = false; // set transmitting flag
+ stop_ook_file_tx(); // stop transmission
+ progressbar.set_value(0); // Reset progress bar to 0
+ button_send_stop.set_text(LanguageHelper::currentMessages[LANG_START]); // set button back to initial "start" state
+}
+
+// `on_file_changed` method: Called when a new file is loaded; parses file data into variables
+void OOKEditorAppView::on_file_changed(const fs::path& new_file_path) {
+ ook_data.payload.clear(); // Clear previous payload content
+ if (!read_ook_file(new_file_path, ook_data)) {
+ text_app_status.set("Error loading " + new_file_path.filename().string());
+ return;
+ }
+ field_frequency.set_value(ook_data.frequency);
+ field_symbol_rate.set_value(ook_data.symbol_rate);
+ field_repeat.set_value(ook_data.repeat);
+ field_pause_symbol_duration.set_value(ook_data.pause_symbol_duration);
+ field_sample_rate.set_by_value(ook_data.sample_rate);
+ text_payload.set(ook_data.payload);
+ button_send_stop.focus();
+ text_app_status.set("Loaded: " + new_file_path.filename().string());
+}
+
+// `on_tx_progress` method: Updates the progress bar based on transmission progress.
+void OOKEditorAppView::on_tx_progress(const uint32_t progress, const bool done) {
+ if (is_transmitting) progressbar.set_value(progress); // Update progress bar value
+ if (done) {
+ stop_tx(); // Stop transmission when progress reaches maximum
+ }
+}
+
+// `draw_waveform` method: Draws the waveform on the UI based on the payload data
+void OOKEditorAppView::draw_waveform() {
+ // Padding reason:
+ // In real-world scenarios, the signal would always start low and return low after turning off the radio.
+ // `waveform_buffer` only controls drawing; the actual send logic is handled by frame_fragments.
+ size_t length = ook_data.payload.length();
+
+ // Ensure waveform length does not exceed buffer size
+ if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) {
+ length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT);
+ }
+
+ // Left padding
+ for (size_t i = 0; i < PADDING_LEFT; i++) {
+ waveform_buffer[i] = 0;
+ }
+
+ // Draw the actual waveform
+ for (size_t n = 0; n < length; n++) {
+ waveform_buffer[n + PADDING_LEFT] = (ook_data.payload[n] == '0') ? 0 : 1;
+ }
+
+ // Right padding
+ for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) {
+ waveform_buffer[i] = 0;
+ }
+
+ waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT);
+ waveform.set_dirty();
+}
+
+// build a new path+file, make some tests, call save_ook_to_file
+void OOKEditorAppView::on_save_file(const std::string value) {
+ // check if there is a payload, else Error
+ if (ook_data.payload.length() < 1) {
+ text_app_status.set("Err: can't save, no payload !");
+ return;
+ }
+ ensure_directory(ook_editor_dir);
+ auto new_path = ook_editor_dir / value + ".OOK";
+ if (save_ook_to_file(new_path)) {
+ text_app_status.set("Saved to " + new_path.string());
+ } else {
+ text_app_status.set("Error saving " + new_path.string());
+ }
+}
+
+// update ook_data from GUI and save
+bool OOKEditorAppView::save_ook_to_file(const std::filesystem::path& path) {
+ update_ook_data_from_app();
+ return save_ook_file(ook_data, path);
+}
+
+// Destructor for `OOKEditorAppView`: Disables the transmitter and shuts down the baseband
+OOKEditorAppView::~OOKEditorAppView() {
+ stop_ook_file_tx();
+ baseband::shutdown();
+}
+
+// Constructor for `OOKEditorAppView`: Sets up the app view and initializes UI elements
+OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
+ : nav_{nav} {
+ // load OOK baseband
+ baseband::run_image(portapack::spi_flash::image_tag_ook);
+
+ // add all the widgets
+ add_children({&field_frequency,
+ &tx_view,
+ &button_send_stop,
+ &label_step,
+ &field_step,
+ &label_sample_rate,
+ &field_sample_rate,
+ &label_symbol_rate,
+ &field_symbol_rate,
+ &label_symbol_rate_unit,
+ &text_payload,
+ &button_set,
+ &progressbar,
+ &label_repeat,
+ &field_repeat,
+ &label_pause_symbol_duration,
+ &field_pause_symbol_duration,
+ &label_pause_symbol_duration_unit,
+ &label_payload,
+ &text_app_status,
+ &label_waveform,
+ &waveform,
+ &button_open,
+ &button_save});
+
+ // Initialize default values for controls
+ field_symbol_rate.set_value(100);
+ field_pause_symbol_duration.set_value(100);
+ field_repeat.set_value(4);
+
+ // Configure open ook file button
+ button_open.on_select = [this](Button&) {
+ auto open_view = nav_.push(".OOK");
+ ensure_directory(ook_editor_dir);
+ open_view->push_dir(ook_editor_dir);
+ open_view->on_changed = [this](std::filesystem::path new_file_path) {
+ // Postpone `on_file_changed` call until `FileLoadView` is closed
+ nav_.set_on_pop([this, new_file_path]() {
+ on_file_changed(new_file_path);
+ button_send_stop.focus();
+ draw_waveform();
+ });
+ };
+ };
+
+ // Configure save to ook file button
+ button_save.on_select = [this, &nav](const ui::Button&) {
+ outputFileBuffer = "";
+ text_prompt(
+ nav,
+ outputFileBuffer,
+ 64,
+ [this](std::string& buffer) {
+ on_save_file(buffer);
+ });
+ };
+
+ // clean out loaded file name if field is changed
+ field_symbol_rate.on_change = [this](int32_t) {
+ text_app_status.set(""); // Clear loaded file text field
+ };
+ // clean out loaded file name if field is changed
+ field_repeat.on_change = [this](int32_t) {
+ text_app_status.set(""); // Clear loaded file text field
+ };
+ // clean out loaded file name if field is changed
+ field_pause_symbol_duration.on_change = [this](int32_t) {
+ text_app_status.set(""); // Clear loaded file text field
+ };
+ // clean out loaded file name if field is changed
+ field_sample_rate.on_change = [this](size_t, int32_t) {
+ text_app_status.set(""); // Clear loaded file text field
+ };
+
+ // setting up FrequencyField
+ field_frequency.set_value(ook_editor_tx_freq);
+
+ // clean out loaded file name if field is changed, save ook_editor_tx_freq
+ field_frequency.on_change = [this](rf::Frequency f) {
+ ook_editor_tx_freq = f;
+ text_app_status.set(""); // Clear loaded file text field
+ };
+
+ // allow typing frequency number
+ field_frequency.on_edit = [this]() {
+ auto freq_view = nav_.push(field_frequency.value());
+ freq_view->on_changed = [this](rf::Frequency f) {
+ field_frequency.set_value(f);
+ text_app_status.set(""); // Clear loaded file text field
+ };
+ };
+
+ // allow different steps on symbol_rate and pause_symbol_duration
+ field_step.on_change = [this](size_t, int32_t value) {
+ text_app_status.set(""); // Clear loaded file text field
+ field_symbol_rate.set_step(value);
+ field_pause_symbol_duration.set_step(value);
+ };
+
+ // Configure button to manually set payload through text input
+ button_set.on_select = [this, &nav](Button&) {
+ text_prompt(
+ nav,
+ ook_data.payload,
+ 100,
+ [this](std::string& s) {
+ text_payload.set(s);
+ draw_waveform();
+ text_app_status.set(""); // Clear loaded file text field
+ });
+ };
+
+ // Configure button to start or stop the transmission
+ button_send_stop.on_select = [this](Button&) {
+ if (!is_transmitting) {
+ update_ook_data_from_app();
+ start_tx(); // Begin transmission
+ } else {
+ stop_tx();
+ }
+ };
+
+ // initial waveform drawing (should be a single line)
+ draw_waveform();
+}
+} // 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
new file mode 100644
index 00000000..34376334
--- /dev/null
+++ b/firmware/application/external/ook_editor/ui_ook_editor.hpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
+ *
+ * This file is part of PortaPack.
+ *
+ */
+
+#ifndef __UI_OOK_EDITOR_H__
+#define __UI_OOK_EDITOR_H__
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_freq_field.hpp"
+#include "ui_textentry.hpp"
+#include "ui_fileman.hpp"
+#include "ui_transmitter.hpp"
+#include "radio_state.hpp"
+#include "app_settings.hpp"
+#include "file_path.hpp"
+#include "ook_file.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::ook_editor {
+
+#define PADDING_LEFT 1 // waveform padding
+#define PADDING_RIGHT 1 // waveform padding
+#define OOK_SAMPLERATE_DEFAULT 2280000U // Set the default Sample Rate
+#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
+#define WAVEFORM_BUFFER_SIZE 550
+
+class OOKEditorAppView : public View {
+ public:
+ void focus() override;
+ OOKEditorAppView(NavigationView& nav);
+
+ ~OOKEditorAppView();
+
+ std::string title() const override {
+ return "OOKEditor";
+ };
+
+ private:
+ NavigationView& nav_; // Reference to the navigation system.
+ uint32_t progress = 0; // Stores the current transmission progress.
+ int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE]; // Buffer for waveform data.
+ bool is_transmitting = false; // State of transmission.
+ rf::Frequency ook_editor_tx_freq{24000000}; // last used transmit frequency
+ std::string outputFileBuffer{}; // buffer for output file
+ ook_file_data ook_data = {0, 0, 0, 0, 0, ""}; // ook files handle
+
+ // draw current payload waveform
+ void draw_waveform();
+
+ // update ook_data from GUI selection
+ void update_ook_data_from_app();
+
+ // start transmission
+ void start_tx();
+ // stop transmission
+ void stop_tx();
+
+ // Updates the transmission progress on the progress bar.
+ void on_tx_progress(const uint32_t progress, const bool done);
+
+ // Updates data when a new file is loaded.
+ void on_file_changed(const std::filesystem::path& new_file_path);
+
+ // Prepare a new std::filesystem::path from string value and call saveFile
+ void on_save_file(const std::string value);
+
+ // Called by on_save_file to save the current OOK parameters to the file
+ bool save_ook_to_file(const std::filesystem::path& path);
+
+ // Registers a message handler for transmission progress updates.
+ MessageHandlerRegistration message_handler_tx_progress{
+ Message::ID::TXProgress, // Transmission progress message ID.
+ [this](const Message* const p) { // Callback to handle the message.
+ const auto message = *reinterpret_cast(p);
+ this->on_tx_progress(message.progress, message.done);
+ }};
+
+ // Sets transmission frequency, bandwidth, and default sample rate for OOK.
+ TxRadioState radio_state_{TRANSMISSION_FREQUENCY_DEFAULT, 1750000, OOK_SAMPLERATE_DEFAULT};
+
+ // Settings manager for app configuration.
+ app_settings::SettingsManager settings_{
+ "ook_editor",
+ app_settings::Mode::TX,
+ {{"ook_editor_tx_freq"sv, &ook_editor_tx_freq}}};
+
+ // UI components for frequency and transmitter view.
+ FrequencyField field_frequency{{0 * 8, 0 * 16}};
+ TransmitterView2 tx_view{{20 * 7, 0 * 16}, true};
+
+ // Labels for various fields such as sample rate and repeat count.
+ Labels label_step{{{170, 20}, "Step:", Theme::getInstance()->fg_light->foreground}};
+ Labels label_sample_rate{{{0, 20}, "SampleRate:", Theme::getInstance()->fg_light->foreground}};
+ Labels label_symbol_rate{{{0, 40}, "SymbolRate:", Theme::getInstance()->fg_light->foreground}};
+ Labels label_symbol_rate_unit{{{132, 40}, "/s", Theme::getInstance()->fg_light->foreground}};
+ Labels label_repeat{{{154, 40}, "Repeat:", Theme::getInstance()->fg_light->foreground}};
+ Labels label_pause_symbol_duration{{{0, 60}, "PauseSymbol:", Theme::getInstance()->fg_light->foreground}};
+ Labels label_pause_symbol_duration_unit{{{132, 60}, "us", Theme::getInstance()->fg_light->foreground}};
+ Labels label_payload{{{0, 80}, "Payload:", Theme::getInstance()->fg_light->foreground}};
+ 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}, ""};
+
+ // OptionsField for selectable sample rates.
+ OptionsField field_sample_rate{{96, 20}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}};
+ // OptionsField for step symbol rates.
+ OptionsField field_step{{210, 20}, 7, {{"1", 1}, {"10", 10}, {"100", 100}}};
+ // Number fields for symbols, pause_symbol between repeat, and repeat count.
+ NumberField field_symbol_rate{{96, 40}, 4, {0, 9999}, 1, '0', false};
+ NumberField field_pause_symbol_duration{{96, 60}, 4, {0, 9999}, 1, '0', false};
+ 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}, ""};
+
+ // 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_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};
+};
+
+}; // namespace ui::external_app::ook_editor
+
+#endif /*__UI_OOK_EDITOR_H__*/
diff --git a/firmware/application/external/ookbrute/main.cpp b/firmware/application/external/ookbrute/main.cpp
new file mode 100644
index 00000000..e323fda2
--- /dev/null
+++ b/firmware/application/external/ookbrute/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_ookbrute.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::ookbrute {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::ookbrute
+
+extern "C" {
+
+__attribute__((section(".external_app.app_ookbrute.application_information"), used)) application_information_t _application_information_ookbrute = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::ookbrute::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "OOKBrute",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::orange().v,
+ /*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/ookbrute/ui_ookbrute.cpp b/firmware/application/external/ookbrute/ui_ookbrute.cpp
new file mode 100644
index 00000000..c997fb6b
--- /dev/null
+++ b/firmware/application/external/ookbrute/ui_ookbrute.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 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_ookbrute.hpp"
+#include "audio.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+
+using namespace portapack;
+using namespace ui;
+
+namespace ui::external_app::ookbrute {
+
+void OOKBruteView::focus() {
+ button_startstop.focus();
+}
+
+OOKBruteView::OOKBruteView(NavigationView& nav)
+ : nav_{nav} {
+ add_children({
+ &button_startstop,
+ &field_frequency,
+ &tx_view,
+ &options_atkmode,
+ &field_start,
+ &field_stop,
+ });
+
+ button_startstop.on_select = [this](Button&) {
+ if (is_running) {
+ is_running = false;
+ stop();
+ } else {
+ is_running = true;
+ start();
+ }
+ };
+
+ options_atkmode.on_change = [this](size_t, int32_t i) {
+ update_start_stop(i);
+ validate_start_stop();
+ };
+ field_start.on_change = [this](int32_t) {
+ validate_start_stop();
+ };
+ field_stop.on_change = [this](int32_t) {
+ validate_start_stop();
+ };
+ update_start_stop(0);
+}
+
+void OOKBruteView::update_start_stop(uint32_t proto) {
+ uint8_t bits = 12;
+ switch (proto) {
+ default:
+ case 0:
+ case 2:
+ case 4:
+ bits = 12;
+ break;
+ case 1:
+ case 3:
+ case 5:
+ bits = 24;
+ break;
+ }
+ uint32_t max = (1 << bits) - 1;
+ field_start.set_range(0, max);
+ field_stop.set_range(0, max);
+ field_start.set_value(0);
+ field_stop.set_value(max);
+}
+
+void OOKBruteView::validate_start_stop() {
+ if (field_start.value() > field_stop.value()) {
+ field_start.set_value(field_stop.value());
+ }
+ if (field_stop.value() < field_start.value()) {
+ field_stop.set_value(field_start.value());
+ }
+}
+
+void OOKBruteView::generate_packet() {
+ uint32_t protocol = options_atkmode.selected_index_value();
+ uint8_t byte = 0;
+ size_t bitstream_length = 0;
+ uint8_t* bitstream = shared_memory.bb_data.data; // max 512 in size
+ uint32_t samples_per_bit = 0; // OOK_SAMPLERATE * bit_duration_in_sec
+ std::string dataFormat = "";
+ std::string zero = "";
+ std::string one = "";
+ uint16_t databits = 0;
+ uint16_t repeat = 1;
+ uint16_t pause_sym = 0;
+ if (protocol == 0) { // came 12
+ samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
+ dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
+ databits = 12;
+ zero = "011";
+ one = "001";
+ repeat = 2;
+ pause_sym = 0;
+ }
+
+ if (protocol == 1) { // came24
+ samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
+ dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
+ databits = 24;
+ zero = "011";
+ one = "001";
+ repeat = 2;
+ pause_sym = 0;
+ }
+ if (protocol == 2) { // nice12
+ samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
+ dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
+ databits = 12;
+ zero = "011";
+ one = "001";
+ repeat = 2;
+ pause_sym = 0;
+ }
+ if (protocol == 3) { // nice24
+ samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
+ dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
+ databits = 24;
+ zero = "011";
+ one = "001";
+ repeat = 2;
+ pause_sym = 0;
+ }
+ if (protocol == 4) { // holtek_ht12
+ samples_per_bit = OOK_SAMPLERATE * (390.0 / 1000000.0);
+ dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC00000000000"; // 36 0 preamble +start bit + data.
+ databits = 12;
+ zero = "011";
+ one = "001";
+ repeat = 2;
+ pause_sym = 0;
+ }
+ if (protocol == 5) { // princeton24
+ samples_per_bit = OOK_SAMPLERATE * (450.0 / 1000000.0); // long = 3*
+ dataFormat = "000000000000000000000000000000000000CCCCCCCCCCCCCCCCCCCCCCCC10000000"; //
+ databits = 24;
+ zero = "1000";
+ one = "1110";
+ repeat = 6;
+ pause_sym = 0;
+ }
+
+ std::string fragments = ""; // storage
+
+ uint16_t cdb = 0; // current data bit
+ for (auto c : dataFormat) { // generate fragments from template
+ if (c == '0') fragments += '0';
+ if (c == '1') fragments += '1';
+ if (c == 'C') {
+ if (counter & (1 << (databits - cdb - 1))) {
+ fragments += one;
+ } else {
+ fragments += zero;
+ }
+ cdb++;
+ }
+ }
+
+ // create bitstream
+ for (auto c : fragments) {
+ byte <<= 1;
+ if (c != '0') byte |= 1;
+ if ((bitstream_length & 7) == 7)
+ bitstream[bitstream_length >> 3] = byte;
+ bitstream_length++;
+ }
+
+ // Finish last byte if needed
+ size_t padding = 8 - (bitstream_length & 7);
+ if (padding != 8) {
+ byte <<= padding;
+ bitstream[(bitstream_length + padding - 1) >> 3] = byte;
+ padding++;
+ }
+
+ // send bitstream
+ baseband::set_ook_data(
+ bitstream_length,
+ samples_per_bit,
+ repeat,
+ pause_sym,
+ 0);
+}
+
+void OOKBruteView::stop() {
+ transmitter_model.disable();
+ baseband::shutdown();
+ button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
+}
+
+void OOKBruteView::start() {
+ counter = field_start.value();
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+ transmitter_model.enable();
+ button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
+ generate_packet();
+}
+
+void OOKBruteView::on_tx_progress(const bool done) {
+ if (done) {
+ if (is_running) {
+ counter++;
+ field_start.set_value(counter);
+ if (counter > (uint32_t)field_stop.value()) {
+ stop();
+ } else {
+ generate_packet();
+ }
+ }
+ }
+}
+
+OOKBruteView::~OOKBruteView() {
+ is_running = false;
+ stop();
+}
+
+} // namespace ui::external_app::ookbrute
+
+/*
+
+https://web.archive.org/web/20230331125843/https://phreakerclub.com/447
+
+
+*/
diff --git a/firmware/application/external/ookbrute/ui_ookbrute.hpp b/firmware/application/external/ookbrute/ui_ookbrute.hpp
new file mode 100644
index 00000000..d5a4c8d8
--- /dev/null
+++ b/firmware/application/external/ookbrute/ui_ookbrute.hpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 HTotoo
+ *
+ * This file is part of PortaPack.
+ *
+ */
+
+#ifndef __UI_OOKBRUTE_H__
+#define __UI_OOKBRUTE_H__
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_navigation.hpp"
+#include "ui_transmitter.hpp"
+#include "ui_freq_field.hpp"
+#include "ui_record_view.hpp"
+#include "app_settings.hpp"
+#include "radio_state.hpp"
+#include "utility.hpp"
+
+using namespace ui;
+
+namespace ui::external_app::ookbrute {
+
+#define OOK_SAMPLERATE 2280000U
+
+class OOKBruteView : public View {
+ public:
+ OOKBruteView(NavigationView& nav);
+ ~OOKBruteView();
+
+ void focus() override;
+
+ std::string title() const override {
+ return "OOKBrute";
+ };
+
+ private:
+ NavigationView& nav_;
+ TxRadioState radio_state_{
+ 433920000 /* frequency */,
+ 1750000 /* bandwidth */,
+ OOK_SAMPLERATE /* sampling rate */
+ };
+
+ TxFrequencyField field_frequency{
+ {0 * 8, 0 * 16},
+ nav_};
+ TransmitterView2 tx_view{
+ {11 * 8, 0 * 16},
+ /*short_ui*/ true};
+ app_settings::SettingsManager settings_{
+ "tx_ookbrute", app_settings::Mode::TX};
+
+ Button button_startstop{
+ {0, 3 * 16, 96, 24},
+ LanguageHelper::currentMessages[LANG_START]};
+
+ NumberField field_start{
+ {0 * 8, 1 * 16},
+ 8,
+ {0, 2500},
+ 1,
+ ' ',
+ true};
+
+ NumberField field_stop{
+ {11 * 8, 1 * 16},
+ 9,
+ {0, 2500},
+ 1,
+ ' ',
+ true};
+
+ OptionsField options_atkmode{
+ {0 * 8, 2 * 16},
+ 12,
+ {{"Came12", 0},
+ {"Came24", 1},
+ {"Nice12", 2},
+ {"Nice24", 3},
+ {"Holtek12", 4},
+ {"Princeton24", 5}}};
+
+ bool is_running{false};
+
+ uint32_t counter = 0; // for packet change
+
+ void start();
+ void stop();
+
+ void on_tx_progress(const bool done);
+ void validate_start_stop();
+ void update_start_stop(uint32_t proto);
+ void generate_packet();
+
+ MessageHandlerRegistration message_handler_tx_progress{
+ Message::ID::TXProgress,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ this->on_tx_progress(message.done);
+ }};
+};
+}; // namespace ui::external_app::ookbrute
+
+#endif /*__UI_OOKBRUTE_H__*/
diff --git a/firmware/application/external/protoview/main.cpp b/firmware/application/external/protoview/main.cpp
index 403bef32..a32b8e1b 100644
--- a/firmware/application/external/protoview/main.cpp
+++ b/firmware/application/external/protoview/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_protoview.application_information"), u
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_protoview */ {'P', 'P', 'V', 'W'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/protoview/ui_protoview.cpp b/firmware/application/external/protoview/ui_protoview.cpp
index d4c9cf18..7519dcc5 100644
--- a/firmware/application/external/protoview/ui_protoview.cpp
+++ b/firmware/application/external/protoview/ui_protoview.cpp
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2017 Furrtek
+ * Copyright (C) 2024 HTotoo, zxkmm
*
* This file is part of PortaPack.
*
@@ -49,10 +48,12 @@ ProtoView::ProtoView(NavigationView& nav)
&field_vga,
&field_volume,
&field_frequency,
- &labels,
+ &label_zoom,
+ &label_shift,
&options_zoom,
&number_shift,
&button_reset,
+ &button_pause,
&waveform,
&waveform2,
&waveform3,
@@ -72,6 +73,10 @@ ProtoView::ProtoView(NavigationView& nav)
button_reset.on_select = [this](Button&) {
reset();
};
+ button_pause.on_select = [this](Button&) {
+ set_pause(!paused);
+ };
+ set_pause(false); // need to use this to default hide shift functionality
baseband::set_subghzd_config(0, receiver_model.sampling_rate());
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
@@ -80,6 +85,7 @@ ProtoView::ProtoView(NavigationView& nav)
void ProtoView::reset() {
cnt = 0;
+ set_pause(false);
number_shift.set_value(0);
waveform_shift = 0;
for (uint16_t i = 0; i < MAXSIGNALBUFFER; i++) time_buffer[i] = 0;
@@ -134,19 +140,20 @@ void ProtoView::draw() {
drawcnt = 0;
for (uint16_t i = 0; i < MAXDRAWCNT; i++) waveform_buffer[i] = 0; // reset
- // add empty data for padding (so you can shift left/nagetive)
- for (int32_t i = 0;
- i < ((waveform_shift > 0) ? 0 : -waveform_shift) && drawcnt < MAXDRAWCNT; // this is for shift nagetive (left side)
- ++i) {
- waveform_buffer[drawcnt++] = 0;
+ // add empty data for padding (so you can shift left/negative)
+ if (waveform_shift < 0) {
+ for (int32_t i = 0; (i < -1 * waveform_shift) && drawcnt < MAXDRAWCNT; // this is for shift negative (move to right)
+ ++i) {
+ waveform_buffer[drawcnt++] = 0;
+ }
}
-
- for (uint16_t i = ((waveform_shift < 0) ? -waveform_shift : 0); // this is for shift positive aka right side
- i < MAXSIGNALBUFFER && drawcnt < MAXDRAWCNT; // prevent out of ranging
+ uint16_t skipped = 0;
+ uint16_t to_skip = ((waveform_shift > 0) ? waveform_shift : 0); // when >0 it'll skip some to move left
+ for (uint16_t i = 0;
+ i < MAXSIGNALBUFFER && drawcnt < MAXDRAWCNT; // prevent out of ranging
++i) {
- uint16_t buffer_index = (i + waveform_shift + MAXSIGNALBUFFER) % MAXSIGNALBUFFER;
- state = time_buffer[buffer_index] >= 0;
- int32_t timeabs = state ? time_buffer[buffer_index] : -1 * time_buffer[buffer_index];
+ state = time_buffer[i] >= 0;
+ int32_t timeabs = state ? time_buffer[i] : -1 * time_buffer[i];
int32_t timesize = timeabs / zoom;
if (timesize == 0) {
remain += timeabs;
@@ -164,7 +171,11 @@ void ProtoView::draw() {
remain = 0;
lmax = 0;
for (int32_t ii = 0; ii < timesize && drawcnt < MAXDRAWCNT; ++ii) {
- waveform_buffer[drawcnt++] = state;
+ if (skipped < to_skip) {
+ skipped++;
+ } else {
+ waveform_buffer[drawcnt++] = state;
+ }
}
}
}
@@ -175,6 +186,7 @@ void ProtoView::add_time(int32_t time) {
}
void ProtoView::on_data(const ProtoViewDataMessage* message) {
+ if (paused) return;
// filter out invalid ones.
uint16_t start = 0;
uint16_t stop = 0;
@@ -207,6 +219,20 @@ void ProtoView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
}
+void ProtoView::set_pause(bool pause) {
+ paused = pause;
+ if (pause) {
+ label_shift.hidden(false);
+ number_shift.hidden(false);
+ button_pause.set_text(LanguageHelper::currentMessages[LANG_RESUME]);
+ } else {
+ label_shift.hidden(true);
+ number_shift.hidden(true);
+ button_pause.set_text(LanguageHelper::currentMessages[LANG_PAUSE]);
+ }
+ set_dirty();
+}
+
ProtoView::~ProtoView() {
audio::output::stop();
receiver_model.disable();
diff --git a/firmware/application/external/protoview/ui_protoview.hpp b/firmware/application/external/protoview/ui_protoview.hpp
index 0af34b1b..b356bcf8 100644
--- a/firmware/application/external/protoview/ui_protoview.hpp
+++ b/firmware/application/external/protoview/ui_protoview.hpp
@@ -74,8 +74,11 @@ class ProtoView : public View {
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
- Labels labels{
- {{0 * 8, 1 * 16}, "Zoom: ", Theme::getInstance()->fg_light->foreground},
+
+ // need to seperate because label shift need to hide independently
+ Labels label_zoom{
+ {{0 * 8, 1 * 16}, "Zoom: ", Theme::getInstance()->fg_light->foreground}};
+ Labels label_shift{
{{0 * 8, 2 * 16}, "Shift: ", Theme::getInstance()->fg_light->foreground}};
OptionsField options_zoom{
@@ -103,6 +106,10 @@ class ProtoView : public View {
{screen_width - 12 * 8, 1 * 16, 96, 24},
LanguageHelper::currentMessages[LANG_RESET]};
+ Button button_pause{
+ {screen_width - 12 * 8, 1 * 16 + 24, 96, 24},
+ LanguageHelper::currentMessages[LANG_PAUSE]};
+
Waveform waveform{
{0, 8 * 8, 240, 50},
waveform_buffer,
@@ -136,6 +143,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
bool needCntReset = false;
+ bool paused = false;
int16_t zoom = 1; // one value in ms
int16_t waveform_shift = 0;
@@ -151,6 +159,7 @@ class ProtoView : public View {
void on_data(const ProtoViewDataMessage* message);
void draw();
void draw2();
+ void set_pause(bool pause);
void reset();
MessageHandlerRegistration message_handler_packet{
@@ -178,4 +187,4 @@ class ProtoView : public View {
} // namespace ui::external_app::protoview
-#endif /*__UI_PROTOVIEW_H__*/
\ No newline at end of file
+#endif /*__UI_PROTOVIEW_H__*/
diff --git a/firmware/application/external/random_password/main.cpp b/firmware/application/external/random_password/main.cpp
new file mode 100644
index 00000000..f92851e1
--- /dev/null
+++ b/firmware/application/external/random_password/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_random_password.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::random_password {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::random_password
+
+extern "C" {
+
+__attribute__((section(".external_app.app_random_password.application_information"), used)) application_information_t _application_information_random_password = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::random_password::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Random passwd",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::yellow().v,
+ /*.menu_location = */ app_location_t::UTILITIES,
+ /*.desired_menu_position = */ -1,
+
+ /*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/random_password/sha512.cpp b/firmware/application/external/random_password/sha512.cpp
new file mode 100644
index 00000000..e4b73ce3
--- /dev/null
+++ b/firmware/application/external/random_password/sha512.cpp
@@ -0,0 +1,156 @@
+/* modified from https://github.com/ulwanski/sha512
+ * copyright @ulwanski
+ * */
+#include "sha512.h"
+
+namespace ui::external_app::random_password {
+
+const unsigned long long SHA512::sha512_k[80] = // ULL = uint64
+ {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+ 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+ 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+ 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+ 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+ 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+ 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+ 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+ 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+ 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+ 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+ 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+ 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+ 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+ 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+ 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+ 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+ 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+ 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+ 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+ 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+ 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+ 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+ 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+ 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+ 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+ 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL};
+
+void SHA512::transform(const unsigned char* message, unsigned int block_nb) {
+ uint64 w[80];
+ uint64 wv[8];
+ uint64 t1, t2;
+ const unsigned char* sub_block;
+ int i, j;
+ for (i = 0; i < (int)block_nb; i++) {
+ sub_block = message + (i << 7);
+ for (j = 0; j < 16; j++) {
+ SHA2_PACK64(&sub_block[j << 3], &w[j]);
+ }
+ for (j = 16; j < 80; j++) {
+ w[j] = SHA512_F4(w[j - 2]) + w[j - 7] + SHA512_F3(w[j - 15]) + w[j - 16];
+ }
+ for (j = 0; j < 8; j++) {
+ wv[j] = m_h[j];
+ }
+ for (j = 0; j < 80; j++) {
+ t1 = wv[7] + SHA512_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j];
+ t2 = SHA512_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]);
+ wv[7] = wv[6];
+ wv[6] = wv[5];
+ wv[5] = wv[4];
+ wv[4] = wv[3] + t1;
+ wv[3] = wv[2];
+ wv[2] = wv[1];
+ wv[1] = wv[0];
+ wv[0] = t1 + t2;
+ }
+ for (j = 0; j < 8; j++) {
+ m_h[j] += wv[j];
+ }
+ }
+}
+
+void SHA512::init() {
+ m_h[0] = 0x6a09e667f3bcc908ULL;
+ m_h[1] = 0xbb67ae8584caa73bULL;
+ m_h[2] = 0x3c6ef372fe94f82bULL;
+ m_h[3] = 0xa54ff53a5f1d36f1ULL;
+ m_h[4] = 0x510e527fade682d1ULL;
+ m_h[5] = 0x9b05688c2b3e6c1fULL;
+ m_h[6] = 0x1f83d9abfb41bd6bULL;
+ m_h[7] = 0x5be0cd19137e2179ULL;
+ m_len = 0;
+ m_tot_len = 0;
+}
+
+void SHA512::update(const unsigned char* message, unsigned int len) {
+ unsigned int block_nb;
+ unsigned int new_len, rem_len, tmp_len;
+ const unsigned char* shifted_message;
+ tmp_len = SHA384_512_BLOCK_SIZE - m_len;
+ rem_len = len < tmp_len ? len : tmp_len;
+ memcpy(&m_block[m_len], message, rem_len);
+ if (m_len + len < SHA384_512_BLOCK_SIZE) {
+ m_len += len;
+ return;
+ }
+ new_len = len - rem_len;
+ block_nb = new_len / SHA384_512_BLOCK_SIZE;
+ shifted_message = message + rem_len;
+ transform(m_block, 1);
+ transform(shifted_message, block_nb);
+ rem_len = new_len % SHA384_512_BLOCK_SIZE;
+ memcpy(m_block, &shifted_message[block_nb << 7], rem_len);
+ m_len = rem_len;
+ m_tot_len += (block_nb + 1) << 7;
+}
+
+void SHA512::final(unsigned char* digest) {
+ unsigned int block_nb;
+ unsigned int pm_len;
+ unsigned int len_b;
+ int i;
+ block_nb = 1 + ((SHA384_512_BLOCK_SIZE - 17) < (m_len % SHA384_512_BLOCK_SIZE));
+ len_b = (m_tot_len + m_len) << 3;
+ pm_len = block_nb << 7;
+ memset(m_block + m_len, 0, pm_len - m_len);
+ m_block[m_len] = 0x80;
+ SHA2_UNPACK32(len_b, m_block + pm_len - 4);
+ transform(m_block, block_nb);
+ for (i = 0; i < 8; i++) {
+ SHA2_UNPACK64(m_h[i], &digest[i << 3]);
+ }
+}
+
+std::string sha512(const void* dat, size_t len) {
+ unsigned char digest[SHA512::DIGEST_SIZE];
+ memset(digest, 0, SHA512::DIGEST_SIZE);
+ SHA512 ctx = SHA512();
+ ctx.init();
+ ctx.update((const unsigned char*)dat, len);
+ ctx.final(digest);
+
+ char buf[2 * SHA512::DIGEST_SIZE + 1];
+ buf[2 * SHA512::DIGEST_SIZE] = 0;
+ for (unsigned int i = 0; i < SHA512::DIGEST_SIZE; i++)
+ sprintf(buf + i * 2, "%02x", digest[i]);
+ return std::string(buf);
+}
+
+std::string sha512(std::string input) {
+ return sha512((const unsigned char*)input.c_str(), (size_t)input.length());
+}
+
+} // namespace ui::external_app::random_password
\ No newline at end of file
diff --git a/firmware/application/external/random_password/sha512.h b/firmware/application/external/random_password/sha512.h
new file mode 100644
index 00000000..a2e3632e
--- /dev/null
+++ b/firmware/application/external/random_password/sha512.h
@@ -0,0 +1,76 @@
+/* modified from https://github.com/ulwanski/sha512
+ * copyright @ulwanski
+ * */
+
+#ifndef SHA512_H
+#define SHA512_H
+
+#include
+#include
+#include
+
+#define _CRT_SECURE_NO_WARNINGS
+
+std::string sha512(const void* dat, size_t len);
+std::string sha512(std::string input);
+
+namespace ui::external_app::random_password {
+
+class SHA512 {
+ protected:
+ typedef unsigned char uint8;
+ typedef unsigned long uint32;
+ typedef unsigned long long uint64;
+ const static uint64 sha512_k[];
+ static const unsigned int SHA384_512_BLOCK_SIZE = (1024 / 8);
+
+ public:
+ void init();
+ void update(const unsigned char* message, unsigned int len);
+ void final(unsigned char* digest);
+ static const unsigned int DIGEST_SIZE = (512 / 8);
+
+ protected:
+ void transform(const unsigned char* message, unsigned int block_nb);
+ unsigned int m_tot_len;
+ unsigned int m_len;
+ unsigned char m_block[2 * SHA384_512_BLOCK_SIZE];
+ uint64 m_h[8];
+};
+
+std::string sha512(std::string input);
+
+#define SHA2_SHFR(x, n) (x >> n)
+#define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))
+#define SHA2_CH(x, y, z) ((x & y) ^ (~x & z))
+#define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define SHA512_F1(x) (SHA2_ROTR(x, 28) ^ SHA2_ROTR(x, 34) ^ SHA2_ROTR(x, 39))
+#define SHA512_F2(x) (SHA2_ROTR(x, 14) ^ SHA2_ROTR(x, 18) ^ SHA2_ROTR(x, 41))
+#define SHA512_F3(x) (SHA2_ROTR(x, 1) ^ SHA2_ROTR(x, 8) ^ SHA2_SHFR(x, 7))
+#define SHA512_F4(x) (SHA2_ROTR(x, 19) ^ SHA2_ROTR(x, 61) ^ SHA2_SHFR(x, 6))
+#define SHA2_UNPACK32(x, str) \
+ { \
+ *((str) + 3) = (uint8)((x)); \
+ *((str) + 2) = (uint8)((x) >> 8); \
+ *((str) + 1) = (uint8)((x) >> 16); \
+ *((str) + 0) = (uint8)((x) >> 24); \
+ }
+#define SHA2_UNPACK64(x, str) \
+ { \
+ *((str) + 7) = (uint8)((x)); \
+ *((str) + 6) = (uint8)((x) >> 8); \
+ *((str) + 5) = (uint8)((x) >> 16); \
+ *((str) + 4) = (uint8)((x) >> 24); \
+ *((str) + 3) = (uint8)((x) >> 32); \
+ *((str) + 2) = (uint8)((x) >> 40); \
+ *((str) + 1) = (uint8)((x) >> 48); \
+ *((str) + 0) = (uint8)((x) >> 56); \
+ }
+#define SHA2_PACK64(str, x) \
+ { \
+ *(x) = ((uint64) * ((str) + 7)) | ((uint64) * ((str) + 6) << 8) | ((uint64) * ((str) + 5) << 16) | ((uint64) * ((str) + 4) << 24) | ((uint64) * ((str) + 3) << 32) | ((uint64) * ((str) + 2) << 40) | ((uint64) * ((str) + 1) << 48) | ((uint64) * ((str) + 0) << 56); \
+ }
+} // namespace ui::external_app::random_password
+
+#endif
\ No newline at end of file
diff --git a/firmware/application/external/random_password/ui_random_password.cpp b/firmware/application/external/random_password/ui_random_password.cpp
new file mode 100644
index 00000000..c36236f6
--- /dev/null
+++ b/firmware/application/external/random_password/ui_random_password.cpp
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2017 Furrtek
+ * Copyright (C) 2024 zxkmm
+ * Copyright (C) 2024 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.
+ */
+
+/* Notes about safety/randomness:
+ * - Radio data is from nature, so if it's evenly tiled in space, it's random enough, but usually it's not
+ * - A unsafe point is the freq that fetch data is from LCG (from Cpp STL) using time as seed, but should be spread out by later random calculation
+ * - A unsafe point is the init data is still using LCG to generate, but used following methods to spread out:
+ * - - Radio data
+ * - - save a buffer of seeds and generate char by char, this cover all the possible chars combination
+ * - - Rollout with teo seeds and get a final char, this spread out again
+ * - - Hash out the generated password and spread out again if you choose. SHA-512 is proven very spreading out in random space by math
+ * - But still, this isn't meant to become a TRNG generator, don't use at critical/high safety requited/important system, this FOSS has absolutely no warranty
+ */
+
+#include "ui_random_password.hpp"
+#include "ui_modemsetup.hpp"
+
+#include "modems.hpp"
+#include "rtc_time.hpp"
+#include "baseband_api.hpp"
+#include "string_format.hpp"
+#include "portapack_persistent_memory.hpp"
+#include "file_path.hpp"
+#include "sha512.h"
+
+using namespace portapack;
+using namespace modems;
+using namespace ui;
+
+namespace ui::external_app::random_password {
+
+void RandomPasswordLogger::log_raw_data(const std::string& data) {
+ log_file.write_entry(data);
+}
+
+void RandomPasswordView::focus() {
+ button_refresh.focus();
+}
+
+RandomPasswordView::RandomPasswordView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base());
+
+ add_children({&rssi,
+ &channel,
+ &field_rf_amp,
+ &field_lna,
+ &field_vga,
+ &field_frequency,
+ &check_log,
+ &button_modem_setup,
+ &labels,
+ &text_generated_passwd,
+ &text_char_type_hints,
+ &check_digits,
+ &check_latin_lower,
+ &check_latin_upper,
+ &check_punctuation,
+ &check_show_seeds,
+ &check_auto_send,
+ &button_refresh,
+ &button_show_qr,
+ &button_flood,
+ &button_send,
+ &field_digits,
+ &field_method,
+ &check_allow_confusable_chars,
+ &text_seed,
+ &progressbar});
+
+ // no idea what's these, i copied from afsk rx app and they seems needed'
+ auto def_bell202 = &modem_defs[0];
+ persistent_memory::set_modem_baudrate(def_bell202->baudrate);
+ serial_format_t serial_format;
+ serial_format.data_bits = 7;
+ serial_format.parity = EVEN;
+ serial_format.stop_bits = 1;
+ serial_format.bit_order = LSB_FIRST;
+ persistent_memory::set_serial_format(serial_format);
+
+ progressbar.set_max(MAX_DIGITS * 2);
+
+ check_log.set_value(logging);
+ check_log.on_select = [this](Checkbox&, bool v) {
+ if (v) {
+ nav_.display_modal(
+ "Warning",
+ "Sure?\n"
+ "this will save all generated\n"
+ "password to sdcard\n"
+ "in plain text\n"
+ "those which generated before\n"
+ "you check me, will lost",
+ YESNO,
+ [this, v](bool c) {
+ if (c) {
+ logging = v;
+ } else {
+ check_log.set_value(false);
+ // this is needed to check back to false cuz when trigger by human, the check to true already happened
+ // this blocked interface so won't accidently saved even if user checked but selected no later here,
+ // but take care of here if in the future implemented ticking/auto/batch save etc
+ }
+ });
+ } else {
+ logging = v;
+ }
+ };
+
+ button_modem_setup.on_select = [&nav](Button&) { // copied from afsk rx app
+ nav.push();
+ };
+
+ check_digits.on_select = [this](Checkbox&, bool) {
+ this->new_password();
+ };
+
+ check_latin_lower.on_select = [this](Checkbox&, bool) {
+ this->new_password();
+ };
+
+ check_latin_upper.on_select = [this](Checkbox&, bool) {
+ this->new_password();
+ };
+
+ check_punctuation.on_select = [this](Checkbox&, bool) {
+ this->new_password();
+ };
+
+ check_allow_confusable_chars.on_select = [this](Checkbox&, bool) {
+ this->new_password();
+ };
+
+ button_refresh.on_select = [this](Button&) {
+ this->set_random_freq();
+ this->new_password();
+ };
+
+ button_show_qr.on_select = [this, &nav](Button&) {
+ nav.push(password.data());
+ };
+
+ button_flood.on_select = [this](Button&) {
+ if (flooding) {
+ flooding = false;
+ button_flood.set_text(LanguageHelper::currentMessages[LANG_FLOOD]);
+ } else {
+ flooding = true;
+ button_flood.set_text(LanguageHelper::currentMessages[LANG_STOP]);
+ }
+ };
+ button_send.on_select = [this, &nav](Button&) {
+ async_prev_val = portapack::async_tx_enabled;
+ portapack::async_tx_enabled = true;
+ UsbSerialAsyncmsg::asyncmsg(password);
+ portapack::async_tx_enabled = async_prev_val;
+ };
+
+ field_digits.on_change = [this](int32_t) {
+ clean_buffer();
+ this->new_password();
+ };
+
+ /// v check defauly val init
+ check_digits.set_value(true);
+ check_latin_lower.set_value(true);
+ check_latin_upper.set_value(true);
+ check_punctuation.set_value(true);
+ check_show_seeds.set_value(true);
+ field_digits.set_value(16);
+ field_method.set_by_value(Method::RADIO_LCG_ROLL_HASH);
+ ///^ check defauly val init
+
+ logger = std::make_unique();
+ if (logger)
+ logger->append(logs_dir / u"random.TXT");
+
+ // Auto-configure modem for LCR RX (will be removed later), copied from afsk rx app
+ baseband::set_afsk(persistent_memory::modem_baudrate(), 8, 0, false);
+
+ receiver_model.enable();
+ receiver_model.set_rf_amp(false);
+ set_random_freq();
+ new_password();
+}
+
+void RandomPasswordView::on_data(uint32_t value, bool is_data) {
+ if (is_data) {
+ seed = static_cast(value);
+ text_seed.set(to_string_dec_uint(check_show_seeds.value() ? seed : 0));
+
+ /// v feed deque
+ seeds_deque.push_back(value);
+ if (seeds_deque.size() > MAX_DIGITS * 2) {
+ seeds_deque.pop_front();
+ }
+
+ ///^ feed deque
+
+ progressbar.set_value(seeds_deque.size());
+
+ if (flooding && seeds_deque.size() >= MAX_DIGITS * 2) {
+ new_password();
+ }
+
+ } else {
+ text_generated_passwd.set("Baudrate estimation: ~");
+ text_char_type_hints.set(to_string_dec_uint(value));
+ }
+}
+
+void RandomPasswordView::clean_buffer() {
+ seeds_deque = {};
+}
+
+void RandomPasswordView::on_freqchg(int64_t freq) {
+ field_frequency.set_value(freq);
+}
+
+void RandomPasswordView::set_random_freq() {
+ std::srand(LPC_RTC->CTIME0);
+ // this is only for seed to visit random freq, the radio is still real random
+
+ auto random_freq = 100000000 + (std::rand() % 900000000); // 100mhz to 1ghz
+ receiver_model.set_target_frequency(random_freq);
+ field_frequency.set_value(random_freq);
+}
+
+void RandomPasswordView::new_password() {
+ if (seeds_deque.size() < MAX_DIGITS * 2) {
+ seeds_buffer_not_full = true;
+ text_generated_passwd.set("wait seeds buffer full");
+ text_char_type_hints.set("then press generate");
+ return;
+ }
+ password = "";
+ std::string charset = "";
+ std::string char_type_hints = "";
+ std::string initial_password = "";
+ int password_length = field_digits.value();
+
+ /// charset worker
+ if (check_digits.value())
+ charset += "0123456789";
+ if (check_latin_lower.value())
+ charset += "abcdefghijklmnopqrstuvwxyz";
+ if (check_latin_upper.value())
+ charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ if (check_punctuation.value())
+ charset += ".,-!?";
+
+ if (!check_allow_confusable_chars.value()) {
+ charset.erase(std::remove_if(charset.begin(), charset.end(),
+ [](char c) { return c == '0' || c == 'O' || c == 'o' || c == '1' || c == 'l'; }),
+ charset.end());
+ }
+
+ if (charset.empty()) {
+ text_generated_passwd.set("generate failed,");
+ text_char_type_hints.set("select at least 1 type");
+ return;
+ }
+
+ /// roll worker
+ for (int i = 0; i < password_length * 2; i += 2) {
+ unsigned int seed = seeds_deque[i];
+ std::srand(seed);
+ uint8_t rollnum = (uint8_t)(seeds_deque[i + 1] % 128);
+ uint8_t nu = 0;
+ for (uint8_t o = 0; o < rollnum; ++o) nu = std::rand();
+ nu++;
+ char c = charset[std::rand() % charset.length()];
+ initial_password += c;
+ }
+
+ // hash worker
+ std::string hashed_password = sha512(initial_password);
+
+ std::vector password_chars(password_length);
+ for (int i = 0; i < password_length; i++) {
+ unsigned int index = std::stoul(hashed_password.substr(i * 2, 2), nullptr, 16) % charset.length(); // result from 0 to charset.length()-1,should be very evenly tiled in the charset space
+ password_chars[i] = charset[index];
+ }
+
+ /// hint text worker
+ for (char c : password_chars) {
+ password += c;
+ if (std::isdigit(c)) {
+ char_type_hints += "1";
+ } else if (std::islower(c)) {
+ char_type_hints += "a";
+ } else if (std::isupper(c)) {
+ char_type_hints += "A";
+ } else {
+ char_type_hints += ",";
+ }
+ }
+
+ /// decision worker
+ switch (field_method.selected_index_value()) {
+ case Method::RADIO_LCG_ROLL:
+ password = initial_password;
+ break;
+ case Method::RADIO_LCG_ROLL_HASH:
+ break;
+ default:
+ break;
+ }
+
+ /// give out result worker
+ text_generated_passwd.set(password);
+ text_char_type_hints.set(char_type_hints);
+
+ paint_password_hints();
+
+ if (logger && logging) {
+ str_log += generate_log_line();
+ logger->log_raw_data(str_log);
+ str_log = "";
+ }
+
+ if (check_auto_send.value() || flooding) {
+ async_prev_val = portapack::async_tx_enabled;
+ portapack::async_tx_enabled = true;
+ UsbSerialAsyncmsg::asyncmsg(password);
+ portapack::async_tx_enabled = async_prev_val;
+ }
+
+ clean_buffer();
+}
+
+void RandomPasswordView::paint_password_hints() {
+ Painter painter;
+ const int char_width = 8;
+ const int char_height = 16;
+ const int start_y = 6 * char_height;
+ const int rect_height = 4;
+
+ for (size_t i = 0; i < password.length(); i++) {
+ char c = password[i];
+ Color color;
+ if (std::isdigit(c)) {
+ color = Color::red();
+ } else if (std::islower(c)) {
+ color = Color::green();
+ } else if (std::isupper(c)) {
+ color = Color::blue();
+ } else {
+ color = Color::white();
+ }
+
+ painter.fill_rectangle(
+ {{static_cast(i) * char_width, start_y},
+ {char_width, rect_height}},
+ color);
+ }
+}
+
+std::string RandomPasswordView::generate_log_line() {
+ std::string seeds_set = "";
+ for (auto seed : seeds_deque) {
+ seeds_set += std::to_string(seed);
+ seeds_set += " ";
+ }
+ std::string line = "\npassword=" + password +
+ "\nseeds=" + seeds_set +
+ "\n";
+ return line;
+}
+
+RandomPasswordView::~RandomPasswordView() {
+ receiver_model.disable();
+ baseband::shutdown();
+}
+
+} // namespace ui::external_app::random_password
\ No newline at end of file
diff --git a/firmware/application/external/random_password/ui_random_password.hpp b/firmware/application/external/random_password/ui_random_password.hpp
new file mode 100644
index 00000000..24ebb63e
--- /dev/null
+++ b/firmware/application/external/random_password/ui_random_password.hpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
+ * Copyright (C) 2017 Furrtek
+ * Copyright (C) 2024 zxkmm
+ * Copyright (C) 2024 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_RANDOM_PASSWORD_H__
+#define __UI_RANDOM_PASSWORD_H__
+
+#define MAX_DIGITS 30
+
+#include "ui.hpp"
+#include "ui_language.hpp"
+#include "ui_navigation.hpp"
+#include "ui_receiver.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_qrcode.hpp"
+#include "usb_serial_asyncmsg.hpp"
+#include "sha512.h"
+
+#include
+#include
+
+using namespace ui;
+
+namespace ui::external_app::random_password {
+
+enum Method {
+ RADIO_LCG_ROLL,
+ RADIO_LCG_ROLL_HASH,
+};
+
+class RandomPasswordLogger {
+ public:
+ Optional append(const std::filesystem::path& filename) {
+ return log_file.append(filename);
+ }
+
+ void log_raw_data(const std::string& data);
+
+ private:
+ LogFile log_file{};
+};
+
+class RandomPasswordView : public View {
+ public:
+ RandomPasswordView(NavigationView& nav);
+ ~RandomPasswordView();
+
+ void focus() override;
+
+ std::string title() const override { return "R.passwd"; };
+
+ private:
+ unsigned int seed = 0; // extern void srand (unsigned int __seed) __THROW;
+ std::string password = "";
+
+ std::deque seeds_deque = {0};
+ bool seeds_buffer_not_full = true;
+ bool in_benchmark = false;
+ bool flooding = false;
+ bool logging = false;
+ bool async_prev_val = false;
+ std::string str_log{""};
+
+ void on_data(uint32_t value, bool is_data);
+ void clean_buffer();
+ void new_password();
+ std::string generate_log_line();
+ void paint_password_hints();
+
+ NavigationView& nav_;
+ RxRadioState radio_state_{};
+ app_settings::SettingsManager settings_{
+ "rx_passgen", app_settings::Mode::RX};
+
+ Labels labels{
+ {{0 * 8, 0 * 16}, "------------seeds-------------", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 3 * 16}, "-----------password-----------", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 7 * 16 - 2}, "digits:", Theme::getInstance()->fg_light->foreground},
+ {{screen_width / 2, 7 * 16 - 2}, "method:", Theme::getInstance()->fg_light->foreground},
+ };
+
+ RFAmpField field_rf_amp{
+ {13 * 8, 1 * 16}};
+ LNAGainField field_lna{
+ {15 * 8, 1 * 16}};
+ VGAGainField field_vga{
+ {18 * 8, 1 * 16}};
+
+ RSSI rssi{
+ {21 * 8, 1 * 16 + 0, 6 * 8, 4}};
+ Channel channel{
+ {21 * 8, 1 * 16 + 5, 6 * 8, 4}};
+
+ RxFrequencyField field_frequency{
+ {0 * 8, 1 * 16},
+ nav_};
+
+ Button button_modem_setup{
+ {screen_width - 12 * 8, 2 * 16 - 1, 96, 16 + 2},
+ LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
+
+ Text text_seed{
+ {0, 2 * 16, 10 * 8, 16},
+ "0000000000"};
+
+ ProgressBar progressbar{
+ {10 * 8 + 2, 2 * 16, screen_width - 96 - (10 * 8 + 4) - 1, 16}};
+
+ Text text_generated_passwd{
+ {0, 4 * 16, screen_width, 16},
+ "000000000000000000000000000000"};
+
+ Text text_char_type_hints{
+ {0, 5 * 16 + 4, screen_width, 16},
+ "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"};
+
+ Checkbox check_show_seeds{
+ {17 * 8, 8 * 16},
+ 6,
+ "Show seed"};
+
+ Checkbox check_auto_send{
+ {1 * 8, 8 * 16},
+ 20,
+ "Auto send"};
+
+ Checkbox check_punctuation{
+ {17 * 8, 12 * 16},
+ 6,
+ ".,-!?"};
+
+ Checkbox check_allow_confusable_chars{
+ {1 * 8, 10 * 16},
+ 20,
+ "0 O o 1 l"};
+
+ Checkbox check_digits{
+ {1 * 8, 12 * 16},
+ 3,
+ "123"};
+
+ Checkbox check_latin_lower{
+ {1 * 8, 14 * 16},
+ 3,
+ "abc"};
+
+ Checkbox check_latin_upper{
+ {17 * 8, 14 * 16},
+ 3,
+ "ABC"};
+
+ Checkbox check_log{
+ {17 * 8, 10 * 16},
+ 3,
+ LanguageHelper::currentMessages[LANG_SAVE]};
+
+ Button button_flood{
+ {0 * 8, 15 * 16 + 18, screen_width / 2, 22},
+ LanguageHelper::currentMessages[LANG_FLOOD]};
+
+ Button button_send{
+ {screen_width / 2 + 2, 15 * 16 + 18, screen_width / 2 - 2, 22},
+ "Send pwd"};
+
+ Button button_refresh{
+ {0 * 8, 17 * 16 + 10, screen_width / 2, 22},
+ "Generate"};
+
+ Button button_show_qr{
+ {screen_width / 2 + 2, 17 * 16 + 10, screen_width / 2 - 2, 22},
+ LanguageHelper::currentMessages[LANG_SHOWQR]};
+
+ NumberField field_digits{
+ {0 + (sizeof("digits:") - 1) * 8, 7 * 16 - 2},
+ 2,
+ {1, 30},
+ 1,
+ ' '};
+
+ OptionsField field_method{
+ {(screen_width / 2) + (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}}};
+
+ void on_data_afsk(const AFSKDataMessage& message);
+
+ std::unique_ptr logger{};
+
+ MessageHandlerRegistration message_handler_packet{
+ Message::ID::AFSKData,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ this->on_data(message->value, message->is_data);
+ }};
+
+ MessageHandlerRegistration message_handler_freqchg{
+ Message::ID::FreqChangeCommand,
+ [this](Message* const p) {
+ const auto message = static_cast(p);
+ this->on_freqchg(message->freq);
+ }};
+
+ void on_freqchg(int64_t freq);
+ void set_random_freq();
+};
+
+} // namespace ui::external_app::random_password
+
+#endif /*__UI_RANDOM_PASSWORD_H__*/
diff --git a/firmware/application/external/remote/main.cpp b/firmware/application/external/remote/main.cpp
new file mode 100644
index 00000000..358452a4
--- /dev/null
+++ b/firmware/application/external/remote/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 gullradriel
+ *
+ * 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_remote.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::remote {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::remote
+
+extern "C" {
+
+__attribute__((section(".external_app.app_remote.application_information"), used)) application_information_t _application_information_remote = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::remote::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Remote",
+ /*.bitmap_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,
+ },
+ /*.icon_color = */ ui::Color::green().v,
+ /*.menu_location = */ app_location_t::HOME,
+ /*.desired_menu_position = */ 4,
+
+ /*.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/apps/ui_remote.cpp b/firmware/application/external/remote/ui_remote.cpp
similarity index 93%
rename from firmware/application/apps/ui_remote.cpp
rename to firmware/application/external/remote/ui_remote.cpp
index ea4c7fd5..b4f7f650 100644
--- a/firmware/application/apps/ui_remote.cpp
+++ b/firmware/application/external/remote/ui_remote.cpp
@@ -20,7 +20,6 @@
*/
#include "ui_remote.hpp"
-
#include "binder.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
@@ -34,11 +33,11 @@
#include "utility.hpp"
#include "file_path.hpp"
+namespace ui::external_app::remote {
+
using namespace portapack;
namespace fs = std::filesystem;
-namespace ui {
-
static constexpr uint8_t text_edit_max = 30;
/* RemoteEntryModel **************************************/
@@ -309,9 +308,9 @@ void RemoteEntryEditView::load_path(std::filesystem::path&& path) {
entry_.metadata = {transmitter_model.target_frequency(), 500'000};
}
-/* RemoteView ********************************************/
+/* RemoteAppView ********************************************/
-RemoteView::RemoteView(
+RemoteAppView::RemoteAppView(
NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_replay);
@@ -365,27 +364,27 @@ RemoteView::RemoteView(
refresh_ui();
}
-RemoteView::RemoteView(NavigationView& nav, fs::path path)
- : RemoteView(nav) {
+RemoteAppView::RemoteAppView(NavigationView& nav, fs::path path)
+ : RemoteAppView(nav) {
load_remote(std::move(path));
refresh_ui();
}
-RemoteView::~RemoteView() {
+RemoteAppView::~RemoteAppView() {
stop();
baseband::shutdown();
save_remote(/*show_error*/ false);
}
-void RemoteView::focus() {
+void RemoteAppView::focus() {
if (model_.entries.empty())
button_add.focus();
else
buttons_[0]->focus();
}
-void RemoteView::create_buttons() {
+void RemoteAppView::create_buttons() {
// Handler callbacks.
auto handle_send = [this](RemoteButton& btn) {
if (btn.entry()->path.empty())
@@ -420,7 +419,7 @@ void RemoteView::create_buttons() {
}
}
-void RemoteView::reset_buttons() {
+void RemoteAppView::reset_buttons() {
// Whever the model's entries instance is invalidated,
// all the pointers in the buttons will end up dangling.
// TODO: This is pretty lame. Could maybe static alloc?
@@ -428,7 +427,7 @@ void RemoteView::reset_buttons() {
btn->set_entry(nullptr);
}
-void RemoteView::refresh_ui() {
+void RemoteAppView::refresh_ui() {
field_title.set_text(model_.name);
field_filename.set_text(remote_path_.stem().string());
@@ -441,7 +440,7 @@ void RemoteView::refresh_ui() {
}
}
-void RemoteView::add_button() {
+void RemoteAppView::add_button() {
if (model_.entries.size() >= max_buttons)
return;
@@ -454,7 +453,7 @@ void RemoteView::add_button() {
set_needs_save();
}
-void RemoteView::edit_button(RemoteButton& btn) {
+void RemoteAppView::edit_button(RemoteButton& btn) {
// Don't let replay thread read the model while editing.
stop();
@@ -471,7 +470,7 @@ void RemoteView::edit_button(RemoteButton& btn) {
};
}
-void RemoteView::send_button(RemoteButton& btn) {
+void RemoteAppView::send_button(RemoteButton& btn) {
// TODO: If this is called while is_sending() == true,
// it just stops and doesn't start the new button?
@@ -510,14 +509,14 @@ void RemoteView::send_button(RemoteButton& btn) {
});
}
-void RemoteView::stop() {
+void RemoteAppView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
ready_signal_ = false;
}
-void RemoteView::new_remote() {
+void RemoteAppView::new_remote() {
save_remote();
init_remote();
refresh_ui();
@@ -526,7 +525,7 @@ void RemoteView::new_remote() {
set_dirty();
}
-void RemoteView::open_remote() {
+void RemoteAppView::open_remote() {
auto open_view = nav_.push(".REM");
open_view->push_dir(remotes_dir);
open_view->on_changed = [this](fs::path path) {
@@ -536,7 +535,7 @@ void RemoteView::open_remote() {
};
}
-void RemoteView::init_remote() {
+void RemoteAppView::init_remote() {
model_ = {"", {}};
reset_buttons();
set_remote_path(next_filename_matching_pattern(remotes_dir / u"REMOTE_????.REM"));
@@ -546,14 +545,14 @@ void RemoteView::init_remote() {
show_error("Couldn't make new remote file.");
}
-bool RemoteView::load_remote(fs::path&& path) {
+bool RemoteAppView::load_remote(fs::path&& path) {
set_remote_path(std::move(path));
set_needs_save(false);
reset_buttons();
return model_.load(remote_path_);
}
-void RemoteView::save_remote(bool show_errors) {
+void RemoteAppView::save_remote(bool show_errors) {
if (!needs_save_)
return;
@@ -564,7 +563,7 @@ void RemoteView::save_remote(bool show_errors) {
set_needs_save(false);
}
-void RemoteView::rename_remote(const std::string& new_name) {
+void RemoteAppView::rename_remote(const std::string& new_name) {
auto folder = remote_path_.parent_path();
auto ext = remote_path_.extension();
auto new_path = folder / new_name + ext;
@@ -581,7 +580,7 @@ void RemoteView::rename_remote(const std::string& new_name) {
set_remote_path(std::move(new_path));
}
-void RemoteView::handle_replay_thread_done(uint32_t return_code) {
+void RemoteAppView::handle_replay_thread_done(uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
if (check_loop.value() && current_btn_) {
send_button(*current_btn_);
@@ -598,15 +597,15 @@ void RemoteView::handle_replay_thread_done(uint32_t return_code) {
stop();
}
-void RemoteView::set_remote_path(fs::path&& path) {
+void RemoteAppView::set_remote_path(fs::path&& path) {
// Unfortunately, have to keep these two in sync because
// settings doesn't know about fs::path.
remote_path_ = std::move(path);
settings_.remote_path = remote_path_.string();
}
-void RemoteView::show_error(const std::string& msg) const {
+void RemoteAppView::show_error(const std::string& msg) const {
nav_.display_modal("Error", msg);
}
-} /* namespace ui */
+} // namespace ui::external_app::remote
diff --git a/firmware/application/apps/ui_remote.hpp b/firmware/application/external/remote/ui_remote.hpp
similarity index 96%
rename from firmware/application/apps/ui_remote.hpp
rename to firmware/application/external/remote/ui_remote.hpp
index 548f8a14..e61834f4 100644
--- a/firmware/application/apps/ui_remote.hpp
+++ b/firmware/application/external/remote/ui_remote.hpp
@@ -41,7 +41,7 @@
#include
#include
-namespace ui {
+namespace ui::external_app::remote {
/* Maps icon index to bitmap. */
class RemoteIcons {
@@ -84,7 +84,7 @@ class RemoteIcons {
&bitmap_icon_sonde,
&bitmap_icon_stealth,
&bitmap_icon_tetra,
- &bitmap_icon_temperature};
+ &bitmap_icon_peripherals_details};
};
// TODO: Use RGB colors instead?
@@ -257,14 +257,14 @@ class RemoteEntryEditView : public View {
};
/* App that allows for buttons to be bound to captures for playback. */
-class RemoteView : public View {
+class RemoteAppView : public View {
public:
- RemoteView(NavigationView& nav);
- RemoteView(NavigationView& nav, std::filesystem::path path);
- ~RemoteView();
+ RemoteAppView(NavigationView& nav);
+ RemoteAppView(NavigationView& nav, std::filesystem::path path);
+ ~RemoteAppView();
- RemoteView(const RemoteView&) = delete;
- RemoteView& operator=(const RemoteView&) = delete;
+ RemoteAppView(const RemoteAppView&) = delete;
+ RemoteAppView& operator=(const RemoteAppView&) = delete;
std::string title() const override { return "Remote"; };
void focus() override;
@@ -385,4 +385,4 @@ class RemoteView : public View {
}};
};
-} /* namespace ui */
+} // namespace ui::external_app::remote
diff --git a/firmware/application/external/shoppingcart_lock/main.cpp b/firmware/application/external/shoppingcart_lock/main.cpp
new file mode 100644
index 00000000..7d7a6292
--- /dev/null
+++ b/firmware/application/external/shoppingcart_lock/main.cpp
@@ -0,0 +1,66 @@
+
+// RocketGod's Shopping Cart Lock app
+// https://betaskynet.com
+#include "ui.hpp"
+#include "shoppingcart_lock.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::shoppingcart_lock {
+void initialize_app(NavigationView& nav) {
+ baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
+ nav.push();
+}
+} // namespace ui::external_app::shoppingcart_lock
+
+extern "C" {
+
+__attribute__((section(".external_app.app_shoppingcart_lock.application_information"), used)) application_information_t _application_information_shoppingcart_lock = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::shoppingcart_lock::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Cart Lock",
+ /*.bitmap_data = */ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7E,
+ 0x7E,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7E,
+ 0x81,
+ 0x81,
+ 0x7E,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7E,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x81,
+ 0x7E,
+ 0x00,
+ },
+ /*.icon_color = */ ui::Color::red().v,
+ /*.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
+};
+}
\ 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
new file mode 100644
index 00000000..22c42988
--- /dev/null
+++ b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp
@@ -0,0 +1,235 @@
+// RocketGod's Shopping Cart Lock app
+// https://betaskynet.com
+#include "shoppingcart_lock.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::shoppingcart_lock {
+
+void ShoppingCartLock::log_event(const std::string& message) {
+ static const size_t MAX_LOG_LINES = 50;
+ static std::vector message_history;
+
+ message_history.push_back(message);
+ if (message_history.size() > MAX_LOG_LINES) {
+ message_history.erase(message_history.begin());
+ menu_view.clear();
+ for (const auto& msg : message_history) {
+ menu_view.add_item({msg,
+ ui::Theme::getInstance()->fg_green->foreground,
+ nullptr,
+ [](KeyEvent) {}});
+ }
+ } else {
+ menu_view.add_item({message,
+ ui::Theme::getInstance()->fg_green->foreground,
+ nullptr,
+ [](KeyEvent) {}});
+ }
+
+ menu_view.set_highlighted(menu_view.item_count() - 1);
+}
+
+bool ShoppingCartLock::is_active() const {
+ return (bool)replay_thread;
+}
+
+void ShoppingCartLock::focus() {
+ menu_view.focus();
+}
+
+void ShoppingCartLock::stop() {
+ log_event(">>> STOP_SEQUENCE_START");
+ if (is_active()) {
+ log_event("... Resetting Replay Thread");
+ replay_thread.reset();
+ }
+ log_event("... Stopping Audio Output");
+ audio::output::stop();
+
+ log_event("... Resetting State Variables");
+ transmitter_model.disable();
+ ready_signal = false;
+ thread_sync_complete = false;
+ looping = false;
+ current_file = "";
+ log_event("<<< STOP_SEQUENCE_COMPLETE");
+}
+
+std::string ShoppingCartLock::list_wav_files() {
+ log_event(">>> WAV_SCAN_START");
+ auto reader = std::make_unique();
+ bool found_lock = false;
+ bool found_unlock = false;
+
+ for (const auto& entry : std::filesystem::directory_iterator(wav_dir, u"*")) {
+ if (std::filesystem::is_regular_file(entry.status())) {
+ auto filename = entry.path().filename().string();
+ std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);
+
+ if (filename == shoppingcart_lock_file || filename == shoppingcart_unlock_file) {
+ std::string file_path = (wav_dir / filename).string();
+ if (reader->open(file_path)) {
+ if (filename == shoppingcart_lock_file) {
+ found_lock = true;
+ log_event("... Found: " + shoppingcart_lock_file);
+ log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
+ log_event("Channels: " + std::to_string(reader->channels()));
+ log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
+ }
+ if (filename == shoppingcart_unlock_file) {
+ found_unlock = true;
+ log_event("... Found: " + shoppingcart_unlock_file);
+ log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
+ log_event("Channels: " + std::to_string(reader->channels()));
+ log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
+ }
+ }
+ }
+
+ if (found_lock && found_unlock) {
+ break;
+ }
+ }
+ }
+
+ if (!found_lock || !found_unlock) {
+ log_event("!!! Missing Required Files:");
+ if (!found_lock) log_event("!!! Missing: " + shoppingcart_lock_file);
+ if (!found_unlock) log_event("!!! Missing: " + shoppingcart_unlock_file);
+ menu_view.hidden(true);
+ text_empty.hidden(false);
+ } else {
+ log_event("... All Required Files Found");
+ menu_view.hidden(false);
+ text_empty.hidden(true);
+ }
+
+ log_event("<<< WAV_SCAN_COMPLETE");
+ return found_lock && found_unlock ? "Required WAV files found" : "Missing required WAV files";
+}
+
+void ShoppingCartLock::wait_for_thread() {
+ uint32_t timeout = 100;
+ while (!ready_signal && timeout > 0) {
+ chThdYield();
+ timeout--;
+ }
+}
+
+void ShoppingCartLock::restart_playback() {
+ auto reader = std::make_unique();
+ std::string file_path = (wav_dir / current_file).string();
+
+ if (!reader->open(file_path)) return;
+
+ replay_thread = std::make_unique(
+ std::move(reader),
+ BUFFER_SIZE,
+ NUM_BUFFERS,
+ &ready_signal,
+ [](uint32_t return_code) {
+ ReplayThreadDoneMessage message{return_code};
+ EventDispatcher::send_message(message);
+ });
+
+ log_event(">> SENDING <<");
+ audio::output::start();
+ transmitter_model.enable();
+}
+
+void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
+ auto reader = std::make_unique();
+ stop();
+
+ std::string file_path = (wav_dir / filename).string();
+ if (!reader->open(file_path)) {
+ nav_.display_modal("Error", "Cannot open " + filename);
+ return;
+ }
+
+ const uint32_t wav_sample_rate = reader->sample_rate();
+ const uint16_t wav_bits_per_sample = reader->bits_per_sample();
+
+ current_file = filename;
+ looping = loop;
+
+ replay_thread = std::make_unique(
+ std::move(reader),
+ BUFFER_SIZE,
+ NUM_BUFFERS,
+ &ready_signal,
+ [](uint32_t return_code) {
+ ReplayThreadDoneMessage message{return_code};
+ EventDispatcher::send_message(message);
+ });
+
+ 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_audiotx_config(
+ bb_sample_rate / decimation,
+ 0.0f,
+ 5.0f,
+ wav_bits_per_sample,
+ wav_bits_per_sample,
+ 0,
+ true,
+ 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 <<<");
+}
+
+ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
+ : nav_{nav} {
+ add_children({&menu_view,
+ &text_empty,
+ &button_lock,
+ &button_unlock,
+ &button_stop});
+
+ button_lock.on_select = [this](Button&) {
+ if (is_active()) stop();
+ log_event(">>> LOCK_SEQUENCE_START");
+ play_audio(shoppingcart_lock_file, true);
+ };
+
+ button_unlock.on_select = [this](Button&) {
+ if (is_active()) stop();
+ log_event(">>> UNLOCK_SEQUENCE_START");
+ play_audio(shoppingcart_unlock_file, true);
+ };
+
+ button_stop.on_select = [this](Button&) {
+ log_event(">>> STOPPING AUDIO");
+ stop();
+ };
+
+ list_wav_files();
+
+ log_event("[+] INITIALIZATION COMPLETE");
+ log_event("[+] PORTAPACK ARMED");
+ log_event("[*] STATUS: READY");
+}
+
+ShoppingCartLock::~ShoppingCartLock() {
+ stop();
+ baseband::shutdown();
+}
+
+} // namespace ui::external_app::shoppingcart_lock
diff --git a/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp
new file mode 100644
index 00000000..abea3ca7
--- /dev/null
+++ b/firmware/application/external/shoppingcart_lock/shoppingcart_lock.hpp
@@ -0,0 +1,102 @@
+// RocketGod's Shopping Cart Lock app
+// https://betaskynet.com
+#pragma once
+
+#include "ui_widget.hpp"
+#include "ui_transmitter.hpp"
+#include "replay_thread.hpp"
+#include "baseband_api.hpp"
+#include "io_wave.hpp"
+#include "audio.hpp"
+#include "portapack_shared_memory.hpp"
+#include "ui_language.hpp"
+#include "file_path.hpp"
+
+namespace ui::external_app::shoppingcart_lock {
+
+class ShoppingCartLock : public View {
+ public:
+ explicit ShoppingCartLock(NavigationView& nav);
+ ~ShoppingCartLock();
+
+ ShoppingCartLock(const ShoppingCartLock&) = delete;
+ ShoppingCartLock& operator=(const ShoppingCartLock&) = delete;
+
+ std::string title() const override { return "Cart Lock"; };
+
+ void focus() override;
+
+ private:
+ static constexpr size_t BUFFER_SIZE = 8192;
+ static constexpr size_t NUM_BUFFERS = 8;
+ const std::string shoppingcart_lock_file{"shopping_cart_lock.wav"};
+ const std::string shoppingcart_unlock_file{"shopping_cart_unlock.wav"};
+
+ NavigationView& nav_;
+ std::unique_ptr replay_thread{};
+ bool ready_signal{false};
+ bool thread_sync_complete{false};
+ bool looping{false};
+ std::string current_file{};
+
+ struct WAVProperties {
+ uint32_t sample_rate;
+ uint16_t bits_per_sample;
+ size_t file_size;
+ };
+
+ void log_event(const std::string& message);
+ std::string list_wav_files();
+ void handle_error(const std::string& message);
+ void play_audio(const std::string& filename, bool loop = false);
+ void stop();
+ bool is_active() const;
+ void wait_for_thread();
+ void restart_playback();
+
+ MenuView menu_view{
+ {0, 0, 240, 150},
+ true};
+
+ Text text_empty{
+ {40, 70, 160, 16},
+ "RocketGod was here"};
+
+ Button button_lock{
+ {40, 165, 160, 35},
+ LanguageHelper::currentMessages[LANG_LOCK]};
+
+ Button button_unlock{
+ {40, 205, 160, 35},
+ LanguageHelper::currentMessages[LANG_UNLOCK]};
+
+ Button button_stop{
+ {40, 245, 160, 35},
+ LanguageHelper::currentMessages[LANG_STOP]};
+
+ MessageHandlerRegistration message_handler_fifo_signal{
+ Message::ID::RequestSignal,
+ [this](const Message* const p) {
+ const auto message = static_cast(p);
+ if (message->signal == RequestSignalMessage::Signal::FillRequest) {
+ ready_signal = true;
+ }
+ }};
+
+ MessageHandlerRegistration message_handler_replay_thread_done{
+ Message::ID::ReplayThreadDone,
+ [this](const Message* const p) {
+ const auto message = *reinterpret_cast(p);
+ if (message.return_code == ReplayThread::END_OF_FILE && looping) {
+ if (is_active()) {
+ chThdSleepMilliseconds(50);
+ restart_playback();
+ }
+ } else {
+ thread_sync_complete = true;
+ stop();
+ }
+ }};
+};
+
+} // namespace ui::external_app::shoppingcart_lock
diff --git a/firmware/application/external/spainter/main.cpp b/firmware/application/external/spainter/main.cpp
index e9678fdd..b12817a6 100644
--- a/firmware/application/external/spainter/main.cpp
+++ b/firmware/application/external/spainter/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_spainter.application_information"), us
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_spainter */ {'P', 'S', 'P', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/spainter/ui_spectrum_painter.hpp b/firmware/application/external/spainter/ui_spectrum_painter.hpp
index af9bd883..63e7e9c9 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __UI_SPECTRUM_PAINTER_H
+#define __UI_SPECTRUM_PAINTER_H
#include "ui.hpp"
#include "ui_language.hpp"
@@ -165,3 +166,5 @@ class SpectrumPainterView : public View {
};
} // namespace ui::external_app::spainter
+
+#endif
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_image.hpp b/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
index 711e8f27..a9c1bf62 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_image.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __UI_SPECTRUM_PAINTER_IMAGE_H
+#define __UI_SPECTRUM_PAINTER_IMAGE_H
#include "ui.hpp"
#include "ui_widget.hpp"
@@ -64,3 +65,5 @@ class SpectrumInputImageView : public View {
};
} // namespace ui::external_app::spainter
+
+#endif
diff --git a/firmware/application/external/spainter/ui_spectrum_painter_text.hpp b/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
index 5a3859ce..f31305d2 100644
--- a/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
+++ b/firmware/application/external/spainter/ui_spectrum_painter_text.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __UI_SPECTRUM_PAINTER_TEXT_H
+#define __UI_SPECTRUM_PAINTER_TEXT_H
#include "ui.hpp"
#include "ui_widget.hpp"
@@ -110,3 +111,5 @@ class SpectrumInputTextView : public View {
};
} // namespace ui::external_app::spainter
+
+#endif
diff --git a/firmware/application/external/sstvtx/main.cpp b/firmware/application/external/sstvtx/main.cpp
index 6a842ffc..887132ba 100644
--- a/firmware/application/external/sstvtx/main.cpp
+++ b/firmware/application/external/sstvtx/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_sstvtx.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_sstvtx */ {'P', 'S', 'T', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/tetris/main.cpp b/firmware/application/external/tetris/main.cpp
index f34a020f..fe66298a 100644
--- a/firmware/application/external/tetris/main.cpp
+++ b/firmware/application/external/tetris/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
},
/*.icon_color = */ ui::Color::orange().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/external/tpmsrx/main.cpp b/firmware/application/external/tpmsrx/main.cpp
index 7c4c4a04..552a14c0 100644
--- a/firmware/application/external/tpmsrx/main.cpp
+++ b/firmware/application/external/tpmsrx/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_tpmsrx.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
+ /*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_tpms */ {'P', 'T', 'P', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
diff --git a/firmware/application/external/tuner/main.cpp b/firmware/application/external/tuner/main.cpp
new file mode 100644
index 00000000..4df2414e
--- /dev/null
+++ b/firmware/application/external/tuner/main.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 Bernd
+ *
+ * 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_tuner.hpp"
+#include "ui_navigation.hpp"
+#include "external_app.hpp"
+
+namespace ui::external_app::tuner {
+void initialize_app(ui::NavigationView& nav) {
+ nav.push();
+}
+} // namespace ui::external_app::tuner
+
+extern "C" {
+
+__attribute__((section(".external_app.app_tuner.application_information"), used)) application_information_t _application_information_tuner = {
+ /*.memory_location = */ (uint8_t*)0x00000000,
+ /*.externalAppEntry = */ ui::external_app::tuner::initialize_app,
+ /*.header_version = */ CURRENT_HEADER_VERSION,
+ /*.app_version = */ VERSION_MD5,
+
+ /*.app_name = */ "Tuner",
+ /*.bitmap_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,
+ },
+ /*.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 */ {'P', 'A', 'B', 'P'},
+ /*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+};
+}
diff --git a/firmware/application/external/tuner/ui_tuner.cpp b/firmware/application/external/tuner/ui_tuner.cpp
new file mode 100644
index 00000000..5aa9e1c0
--- /dev/null
+++ b/firmware/application/external/tuner/ui_tuner.cpp
@@ -0,0 +1,256 @@
+/*
+ * copyleft 2024 sommermorgentraum
+ *
+ * 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_tuner.hpp"
+#include "baseband_api.hpp"
+#include "audio.hpp"
+#include "portapack.hpp"
+
+using namespace portapack;
+
+namespace ui::external_app::tuner {
+
+TunerView::TunerView(NavigationView& nav)
+ : nav_{nav} {
+ baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
+
+ add_children({
+ &labels,
+ &field_volume,
+ &options_instrument,
+ &options_note,
+ &button_play_stop,
+ &text_note_frequency,
+ &text_note_octave_shift,
+ });
+
+ audio::set_rate(audio::Rate::Hz_24000);
+
+ options_instrument.on_change = [this](size_t, int32_t value) {
+ const Instrument* selected_instrument = nullptr;
+
+ switch (value) {
+ case 0:
+ selected_instrument = &guitar;
+ break;
+ case 1:
+ selected_instrument = &violin;
+ break;
+ case 2:
+ selected_instrument = &pitch_fork;
+ break;
+ }
+
+ if (selected_instrument) {
+ update_notes_for_instrument(*selected_instrument);
+ }
+
+ update_current_note();
+ };
+
+ options_note.on_change = [this](size_t, int32_t index) {
+ const Instrument* current_instrument = nullptr;
+ switch (options_instrument.selected_index_value()) {
+ case 0:
+ current_instrument = &guitar;
+ break;
+ case 1:
+ current_instrument = &violin;
+ break;
+ case 2:
+ current_instrument = &pitch_fork;
+ break;
+ }
+
+ if (current_instrument) {
+ auto it = current_instrument->notes.begin();
+ std::advance(it, index);
+ if (it != current_instrument->notes.end()) {
+ current_note_frequency = it->second.frequency;
+ current_note_sample_rate = it->second.sample_rate;
+ current_note_octave_shift = it->second.octave_shift;
+ }
+ }
+
+ update_current_note();
+ };
+
+ button_play_stop.on_select = [this]() {
+ if (current_note_playing) {
+ stop_play();
+ } else {
+ play_change_note();
+ }
+ };
+
+ options_instrument.set_selected_index(0);
+ update_notes_for_instrument(guitar);
+ update_current_note();
+
+ field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
+ field_volume.set_value(99);
+
+ audio::set_rate(audio::Rate::Hz_24000);
+ audio::output::start();
+}
+
+TunerView::~TunerView() {
+ receiver_model.disable();
+ baseband::shutdown();
+ audio::output::stop();
+}
+
+void TunerView::focus() {
+ options_instrument.focus();
+}
+
+void TunerView::update_notes_for_instrument(const Instrument& instrument) {
+ std::vector note_options;
+ size_t index = 0;
+
+ for (const auto& note_pair : instrument.notes) {
+ note_options.emplace_back(OptionsField::option_t{
+ note_pair.first,
+ index++});
+ }
+
+ options_note.set_options(note_options);
+ if (!note_options.empty()) {
+ options_note.set_selected_index(0);
+ }
+}
+
+void TunerView::update_current_note() {
+ const Instrument* current_instrument = nullptr;
+
+ switch (options_instrument.selected_index_value()) {
+ case 0:
+ current_instrument = &guitar;
+ break;
+ case 1:
+ current_instrument = &violin;
+ break;
+ case 2:
+ current_instrument = &pitch_fork;
+ break;
+ }
+
+ if (current_instrument) {
+ std::string note_name = options_note.selected_index_name();
+
+ // map
+ auto note_it = current_instrument->notes.find(note_name);
+ if (note_it != current_instrument->notes.end()) {
+ current_note_frequency = note_it->second.frequency;
+ current_note_sample_rate = note_it->second.sample_rate;
+ current_note_octave_shift = note_it->second.octave_shift;
+
+ text_note_frequency.set(std::to_string(current_note_frequency));
+ text_note_octave_shift.set(std::to_string(current_note_octave_shift));
+
+ set_dirty();
+
+ if (current_note_playing) {
+ play_change_note();
+ }
+ }
+ }
+}
+
+void TunerView::stop_play() {
+ baseband::request_beep_stop();
+ current_note_playing = false;
+ button_play_stop.set_bitmap(&bitmap_icon_replay);
+}
+
+void TunerView::play_change_note() {
+ if (current_note_playing) {
+ baseband::request_beep_stop();
+ audio::set_rate(current_note_sample_rate);
+ baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
+ } else {
+ audio::set_rate(current_note_sample_rate);
+ baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
+ }
+ current_note_playing = true;
+ button_play_stop.set_bitmap(&bitmap_icon_sleep);
+ set_dirty();
+}
+
+uint32_t TunerView::protected_sample_rate(audio::Rate r) {
+ switch (r) {
+ case audio::Rate::Hz_12000:
+ return 12000;
+ case audio::Rate::Hz_24000:
+ return 24000;
+ case audio::Rate::Hz_48000:
+ return 48000;
+ default:
+ return 24000;
+ }
+}
+
+void TunerView::paint(Painter& painter) {
+ View::paint(painter);
+
+ painter.fill_rectangle(
+ {screen_width / 4, 8 * 16, screen_width / 2, 6 * 16},
+ Theme::getInstance()->bg_darkest->background);
+
+ if (!current_note_playing) return;
+
+ painter.fill_rectangle(
+ {screen_width / 4, 10 * 16, 2, 2 * 16},
+ Theme::getInstance()->fg_light->foreground);
+
+ painter.fill_rectangle(
+ {screen_width / 4, 12 * 16, screen_width / 4 * 2 + 2, 2},
+ Theme::getInstance()->fg_light->foreground);
+
+ painter.fill_rectangle(
+ {(screen_width / 4) * 3, 10 * 16, 2, 2 * 16},
+ Theme::getInstance()->fg_light->foreground);
+
+ painter.draw_string(
+ {screen_width / 4 - 2 * 8, 8 * 16},
+ (current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
+ std::to_string(current_note_frequency));
+
+ int16_t real_frequency = current_note_frequency;
+ if (current_note_octave_shift > 0) {
+ for (int i = 0; i < current_note_octave_shift; i++) {
+ real_frequency *= 2;
+ }
+ } else if (current_note_octave_shift < 0) {
+ for (int i = 0; i > current_note_octave_shift; i--) {
+ real_frequency /= 2;
+ }
+ }
+ painter.draw_string({(screen_width / 4) * 3 - 2 * 8, 8 * 16},
+ (current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
+ std::to_string(real_frequency));
+
+ painter.draw_string({screen_width / 2 - 3 * 16, 13 * 16},
+ (current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
+ std::to_string(current_note_octave_shift) + " * 8ev");
+}
+
+} // namespace ui::external_app::tuner
diff --git a/firmware/application/external/tuner/ui_tuner.hpp b/firmware/application/external/tuner/ui_tuner.hpp
new file mode 100644
index 00000000..7b76d040
--- /dev/null
+++ b/firmware/application/external/tuner/ui_tuner.hpp
@@ -0,0 +1,145 @@
+/*
+ * copyleft 2024 sommermorgentraum
+ *
+ * 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_TUNER_H__
+#define __UI_TUNER_H__
+
+#include "ui_navigation.hpp"
+#include "ui_receiver.hpp"
+#include "audio.hpp"
+
+namespace ui::external_app::tuner {
+
+struct Instrument {
+ std::string name;
+ struct NoteInfo {
+ uint16_t frequency;
+ audio::Rate sample_rate; // tune samplerate to allow for freqs
+ int8_t octave_shift;
+ // PP hardware can't handle some extremely low/high frequencies, this indicates how much the freq that were played is shifted up/down,
+ // for example, if shift is -2 and note is A3, it means the real string should play A5 but PP's speaker plays A3
+ };
+ std::map notes; // this is for fast looking : O(log(n)) , usage `auto note = guitar.notes["A4"];`
+};
+
+class TunerView : public View {
+ public:
+ TunerView(NavigationView& nav);
+ ~TunerView();
+ TunerView(const TunerView& other) = delete;
+ TunerView& operator=(const TunerView& other) = delete;
+
+ void focus() override;
+ void update_audio_beep();
+
+ std::string title() const override { return "Tuner"; };
+
+ private:
+ NavigationView& nav_;
+ bool beep{false};
+
+ void update_notes_for_instrument(const Instrument& instrument);
+ void play_change_note();
+ void stop_play();
+ void update_current_note();
+ uint32_t protected_sample_rate(audio::Rate r);
+ void paint(Painter& painter) override;
+
+ uint16_t current_note_frequency{440};
+ audio::Rate current_note_sample_rate{audio::Rate::Hz_12000};
+ int8_t current_note_octave_shift{0};
+ bool current_note_playing{false};
+
+ Labels labels{
+ {{0 * 8, 1 * 16}, "Instrument:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 2 * 16}, "Note:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 3 * 16}, "Note Frequency:", Theme::getInstance()->fg_light->foreground},
+ {{0 * 8, 4 * 16}, "Note Octave Shift:", Theme::getInstance()->fg_light->foreground},
+ {{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},
+ "",
+ };
+
+ Text text_note_octave_shift{
+ {(sizeof("Note Octave Shift:") + 1) * 8, 4 * 16, screen_width - (sizeof("Note Octave Shift:") + 1) * 8, 16},
+ "",
+ };
+
+ AudioVolumeField field_volume{
+ {(sizeof("Volume:") + 1) * 8, 5 * 16}};
+
+ // TODO: load list runtime
+ OptionsField options_instrument{
+ {(sizeof("Instrument:") + 1) * 8, 1 * 16},
+ screen_width - (sizeof("Instrument:") + 1) * 8,
+ {{"Guitar", 0},
+ {"Violin", 1},
+ {"Pitch Fork", 2}}};
+
+ OptionsField options_note{
+ {(sizeof("Note:") + 1) * 8, 2 * 16},
+ screen_width - (sizeof("Note:") + 1) * 8,
+ {}};
+
+ NewButton button_play_stop{
+ {0 * 8, 16 * 16, screen_width, screen_height - 16 * 16},
+ {},
+ &bitmap_icon_replay,
+ Theme::getInstance()->fg_red->foreground};
+
+ // TODO: load from file DB
+ const Instrument guitar = {
+ .name = "Folk Guitar",
+ .notes = {
+ {"E2", {165, audio::Rate::Hz_12000, -1}}, // actual: E2, PP speaker: E3
+ {"A2", {110, audio::Rate::Hz_12000, 0}},
+ {"D3", {147, audio::Rate::Hz_12000, 0}},
+ {"G3", {196, audio::Rate::Hz_24000, 0}},
+ {"B3", {247, audio::Rate::Hz_24000, 0}},
+ {"E4", {330, audio::Rate::Hz_24000, 0}}}};
+
+ const Instrument violin = {
+ .name = "Violin 440 Standard, 12ET",
+ .notes = {
+ {"G3", {196, audio::Rate::Hz_12000, 0}},
+ {"D4", {294, audio::Rate::Hz_24000, 0}},
+ {"A4", {440, audio::Rate::Hz_48000, 0}},
+ {"E5", {659, audio::Rate::Hz_48000, 0}}}};
+
+ const Instrument pitch_fork = {// freq copied from flipper app
+ .name = "Pitch Fork",
+ .notes = {
+ {"12ET A4", {440, audio::Rate::Hz_48000, 0}},
+ {"Sarti's A4", {436, audio::Rate::Hz_48000, 0}},
+ {"1858 A4", {435, audio::Rate::Hz_48000, 0}},
+ {"Verdi's A4", {432, audio::Rate::Hz_48000, 0}}}};
+
+ std::map instruments{
+ {"Guitar", guitar},
+ {"Violin", violin},
+ {"Pitch Fork", pitch_fork}};
+};
+
+} // namespace ui::external_app::tuner
+
+#endif /*__UI_TUNER_H__*/
diff --git a/firmware/application/external/wardrivemap/main.cpp b/firmware/application/external/wardrivemap/main.cpp
index fb5fb7eb..4da13113 100644
--- a/firmware/application/external/wardrivemap/main.cpp
+++ b/firmware/application/external/wardrivemap/main.cpp
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_wardrivemap.application_information"),
},
/*.icon_color = */ ui::Color::yellow().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/file_path.cpp b/firmware/application/file_path.cpp
index a56e141f..ce4acf48 100644
--- a/firmware/application/file_path.cpp
+++ b/firmware/application/file_path.cpp
@@ -30,6 +30,7 @@ const std::filesystem::path audio_dir = u"AUDIO";
const std::filesystem::path blerx_dir = u"BLERX";
const std::filesystem::path bletx_dir = u"BLETX";
const std::filesystem::path captures_dir = u"CAPTURES";
+const std::filesystem::path cvsfiles_dir = u"CVSFILES";
const std::filesystem::path debug_dir = u"DEBUG";
const std::filesystem::path firmware_dir = u"FIRMWARE";
const std::filesystem::path freqman_dir = u"FREQMAN";
@@ -47,3 +48,4 @@ const std::filesystem::path splash_dir = u"SPLASH";
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";
diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp
index 802ee868..bc115736 100644
--- a/firmware/application/file_path.hpp
+++ b/firmware/application/file_path.hpp
@@ -32,6 +32,7 @@ extern const std::filesystem::path audio_dir;
extern const std::filesystem::path blerx_dir;
extern const std::filesystem::path bletx_dir;
extern const std::filesystem::path captures_dir;
+extern const std::filesystem::path cvsfiles_dir;
extern const std::filesystem::path debug_dir;
extern const std::filesystem::path firmware_dir;
extern const std::filesystem::path freqman_dir;
@@ -49,5 +50,6 @@ extern const std::filesystem::path splash_dir;
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;
#endif /* __FILE_PATH_H__ */
diff --git a/firmware/application/flipper_subfile.cpp b/firmware/application/flipper_subfile.cpp
index 7cca20cc..fe57d976 100644
--- a/firmware/application/flipper_subfile.cpp
+++ b/firmware/application/flipper_subfile.cpp
@@ -29,13 +29,16 @@
namespace fs = std::filesystem;
using namespace std::literals;
-const std::string_view filetype_name = "Filetype"sv;
-const std::string_view frequency_name = "Frequency"sv;
-const std::string_view latitude_name = "Latitute"sv;
-const std::string_view longitude_name = "Longitude"sv;
-const std::string_view protocol_name = "Protocol"sv;
-const std::string_view preset_name = "Preset"sv;
-const std::string_view te_name = "TE"sv; // only in BinRAW
+const std::string filetype_name = "Filetype";
+const std::string frequency_name = "Frequency";
+const std::string latitude_name_old = "Latitute";
+const std::string longitude_name_old = "Longitude";
+const std::string latitude_name = "Lat";
+const std::string longitude_name = "Lon";
+const std::string protocol_name = "Protocol";
+const std::string preset_name = "Preset";
+const std::string te_name = "TE"; // only in BinRAW
+const std::string bit_count_name = "Bit"; // for us, only in BinRAW
/*
Filetype: Flipper SubGhz Key File
@@ -70,35 +73,54 @@ raw_data- positive: carrier for n time, negative: no carrier for n time. (us)
Optional read_flippersub_file(const fs::path& path) {
File f;
auto error = f.open(path);
-
if (error)
return {};
-
flippersub_metadata metadata{};
- auto reader = FileLineReader(f);
- for (const auto& line : reader) {
- auto cols = split_string(line, ':');
-
- if (cols.size() != 2)
+ char ch = 0;
+ std::string line = "";
+ auto fr = f.read(&ch, 1);
+ while (!fr.is_error() && fr.value() > 0) {
+ if (line.length() < 130 && ch != '\n') line += ch;
+ if (ch != '\n') {
+ fr = f.read(&ch, 1);
+ continue;
+ }
+ auto it = line.find(':', 0);
+ if (it == std::string::npos) {
+ fr = f.read(&ch, 1);
continue; // Bad line.
- if (cols[1].length() <= 1) continue;
- std::string fixed = cols[1].data() + 1;
+ }
+ std::string fixed = line.data() + it + 1;
fixed = trim(fixed);
- if (cols[0] == filetype_name) {
+ std::string head = line.substr(0, it);
+ line = "";
+
+ if (fixed.length() <= 1) {
+ fr = f.read(&ch, 1);
+ continue;
+ }
+
+ if (head == filetype_name) {
if (fixed != "Flipper SubGhz Key File" && fixed != "Flipper SubGhz RAW File") return {}; // not supported
- } else if (cols[0] == frequency_name)
+ } else if (head == frequency_name)
parse_int(fixed, metadata.center_frequency);
- else if (cols[0] == latitude_name)
+ else if (head == latitude_name)
parse_float_meta(fixed, metadata.latitude);
- else if (cols[0] == longitude_name)
+ else if (head == longitude_name)
parse_float_meta(fixed, metadata.longitude);
- else if (cols[0] == protocol_name) {
+ else if (head == latitude_name_old)
+ parse_float_meta(fixed, metadata.latitude);
+ else if (head == longitude_name_old)
+ parse_float_meta(fixed, metadata.longitude);
+ else if (head == protocol_name) {
if (fixed == "RAW") metadata.protocol = FLIPPER_PROTO_RAW;
if (fixed == "BinRAW") metadata.protocol = FLIPPER_PROTO_BINRAW;
- } else if (cols[0] == te_name) {
+ } else if (head == te_name) {
metadata.te = atoi(fixed.c_str());
- } else if (cols[0] == preset_name) {
+ } else if (head == bit_count_name) {
+ metadata.binraw_bit_count = atol(fixed.c_str());
+ } else if (head == preset_name) {
if (fixed.find("FSK") != std::string::npos) {
metadata.preset = FLIPPER_PRESET_2FSK;
} else if (fixed.find("Ook") != std::string::npos) {
@@ -106,12 +128,92 @@ Optional read_flippersub_file(const fs::path& path) {
} else if (fixed.find("Custom") != std::string::npos) {
metadata.preset = FLIPPER_PRESET_CUSTOM;
}
-
- } else
- continue;
+ }
+ fr = f.read(&ch, 1);
}
-
+ f.close();
if (metadata.center_frequency == 0) return {}; // Parse failed.
return metadata;
}
+
+bool seek_flipper_raw_first_data(File& f) {
+ f.seek(0);
+ std::string chs = "";
+ char ch;
+ while (f.read(&ch, 1)) {
+ if (ch == '\r') continue;
+ if (ch == '\n') {
+ chs = "";
+ continue;
+ };
+ chs += ch;
+ if (ch == 0) break;
+ if (chs == "RAW_Data: ") {
+ return true;
+ }
+ }
+ return false;
+}
+bool seek_flipper_binraw_first_data(File& f, bool seekzero) {
+ if (seekzero) f.seek(0);
+ std::string chs = "";
+ char ch;
+ while (f.read(&ch, 1)) {
+ if (ch == '\r') continue;
+ if (ch == '\n') {
+ chs = "";
+ continue;
+ };
+ if (ch == 0) break;
+ chs += ch;
+ if (chs == "Data_RAW: ") {
+ return true;
+ }
+ }
+ return false;
+}
+
+Optional read_flipper_raw_next_data(File& f) {
+ // RAW_Data: 5832 -12188 130 -162
+ std::string chs = "";
+ char ch = 0;
+ while (f.read(&ch, 1).is_ok()) {
+ if (ch == '\r') continue; // should not present
+ if ((ch == ' ') || ch == '\n') {
+ if (chs == "RAW_Data:") {
+ chs = "";
+ continue;
+ }
+ break;
+ };
+ if (ch == 0) break;
+ chs += ch;
+ }
+ if (chs == "") return {};
+ return atol(chs.c_str());
+}
+
+Optional read_flipper_binraw_next_data(File& f) {
+ // Data_RAW: 02 10 84 BUT THERE ARE Bit_RAW lines to skip!
+ std::string chs = "";
+ char ch = 0;
+ while (f.read(&ch, 1)) {
+ if (ch == '\r') continue; // should not present
+ if ((ch == ' ') || ch == '\n') {
+ if (chs == "RAW_Data:") {
+ chs = "";
+ continue;
+ }
+ break;
+ };
+ if (ch == 0) break;
+ chs += ch;
+ }
+ if (chs == "") return {};
+ return static_cast(std::stoul(chs, nullptr, 16));
+}
+
+bool get_flipper_binraw_bitvalue(uint8_t byte, uint8_t nthBit) {
+ return (byte & (1 << nthBit)) != 0;
+}
\ No newline at end of file
diff --git a/firmware/application/flipper_subfile.hpp b/firmware/application/flipper_subfile.hpp
index 0a123edf..3681c849 100644
--- a/firmware/application/flipper_subfile.hpp
+++ b/firmware/application/flipper_subfile.hpp
@@ -44,9 +44,15 @@ struct flippersub_metadata {
FlipperProto protocol = FLIPPER_PROTO_UNSUPPORTED;
FlipperPreset preset = FLIPPER_PRESET_UNK;
uint16_t te = 0;
+ uint32_t binraw_bit_count = 0;
};
Optional read_flippersub_file(const std::filesystem::path& path);
+bool seek_flipper_raw_first_data(File& f);
+bool seek_flipper_binraw_first_data(File& f, bool seekzero = true);
+Optional read_flipper_raw_next_data(File& f);
+Optional read_flipper_binraw_next_data(File& f);
+bool get_flipper_binraw_bitvalue(uint8_t byte, uint8_t nthBit);
// Maybe sometime there will be a data part reader / converter
diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp
index 17f3d66b..63632ce3 100644
--- a/firmware/application/freqman.cpp
+++ b/firmware/application/freqman.cpp
@@ -28,8 +28,11 @@
using option_t = ui::OptionsField::option_t;
using options_t = ui::OptionsField::options_t;
-extern options_t freqman_steps;
-extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
+using option_db_t = std::pair;
+using options_db_t = std::vector;
+
+extern options_db_t freqman_steps;
+extern const option_db_t* find_by_index(const options_db_t& options, freqman_index_t index);
/* Option value lookup. */
int32_t freqman_entry_get_step_value(freqman_index_t step) {
diff --git a/firmware/application/freqman_db.cpp b/firmware/application/freqman_db.cpp
index 61a071e5..89eb52b1 100644
--- a/firmware/application/freqman_db.cpp
+++ b/firmware/application/freqman_db.cpp
@@ -41,7 +41,7 @@ namespace fs = std::filesystem;
const std::filesystem::path freqman_extension{u".TXT"};
// NB: Don't include UI headers to keep this code unit testable.
-using option_t = std::pair;
+using option_t = std::pair;
using options_t = std::vector;
options_t freqman_modulations = {
@@ -194,27 +194,27 @@ bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
if (auto opt = find_by_index(freqman_modulations, modulation))
- return opt->first;
+ return (std::string)opt->first;
return {};
}
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth) {
if (modulation < freqman_modulations.size()) {
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
- return opt->first;
+ return (std::string)opt->first;
}
return {};
}
std::string freqman_entry_get_step_string(freqman_index_t step) {
if (auto opt = find_by_index(freqman_steps, step))
- return opt->first;
+ return (std::string)opt->first;
return {};
}
std::string freqman_entry_get_step_string_short(freqman_index_t step) {
if (auto opt = find_by_index(freqman_steps_short, step))
- return opt->first;
+ return (std::string)opt->first;
return {};
}
diff --git a/firmware/application/i2c_device_to_host.c b/firmware/application/i2c_device_to_host.c
new file mode 100644
index 00000000..b243b9a5
--- /dev/null
+++ b/firmware/application/i2c_device_to_host.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ * Copyright (C) 2024 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 "i2c_device_to_host.h"
+#include "i2cdevmanager_c_api.h"
+#include
+
+I2CShellDriver I2CD1;
+
+// pp to i2c data tx
+static void onotifyi2c(GenericQueue* qp) {
+ I2CShellDriver* sdp = chQGetLink(qp);
+ uint8_t buff[I2CSHELL_BUFFERS_SIZE];
+ int n = chOQGetFullI(&sdp->oqueue);
+ if (n > I2CSHELL_BUFFERS_SIZE) n = I2CSHELL_BUFFERS_SIZE; // don't overflow
+ if (n > 0) {
+ for (int i = 0; i < n; i++) {
+ buff[i] = chOQGetI(&sdp->oqueue);
+ }
+ int ret;
+ chSysUnlock();
+ do {
+ ret = oNofityI2cFromShell(&buff[0], n); // i2c_write
+ if (ret == -1)
+ chThdSleepMilliseconds(1);
+ } while (ret == -1);
+ chSysLock();
+ }
+}
+
+static size_t write(void* ip, const uint8_t* bp, size_t n) {
+ return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp,
+ n, TIME_INFINITE);
+}
+
+static size_t read(void* ip, uint8_t* bp, size_t n) {
+ return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp,
+ n, TIME_INFINITE);
+}
+
+static msg_t put(void* ip, uint8_t b) {
+ return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, TIME_INFINITE);
+}
+
+static msg_t get(void* ip) {
+ return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, TIME_INFINITE);
+}
+
+static msg_t putt(void* ip, uint8_t b, systime_t timeout) {
+ return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, timeout);
+}
+
+static msg_t gett(void* ip, systime_t timeout) {
+ return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, timeout);
+}
+
+static size_t writet(void* ip, const uint8_t* bp, size_t n, systime_t time) {
+ return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp, n, time);
+}
+
+static size_t readt(void* ip, uint8_t* bp, size_t n, systime_t time) {
+ return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp, n, time);
+}
+
+static const struct I2CShellDriverVMT vmt = {
+ write, read, put, get,
+ putt, gett, writet, readt};
+
+void init_i2c_shell_driver(I2CShellDriver* sdp) {
+ sdp->vmt = &vmt;
+ chIQInit(&sdp->iqueue, sdp->ib, I2CSHELL_BUFFERS_SIZE, NULL, sdp);
+ chOQInit(&sdp->oqueue, sdp->ob, I2CSHELL_BUFFERS_SIZE, onotifyi2c, sdp);
+}
+
+// i2c->pp data rx
+void complete_i2chost_to_device_transfer(uint8_t* data, size_t length) {
+ chSysLock();
+ for (unsigned int i = 0; i < length; i++) {
+ msg_t ret;
+ do {
+ ret = chIQPutI(&I2CD1.iqueue, data[i]);
+ if (ret == Q_FULL) {
+ chSysUnlock();
+ chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
+ chSysLock();
+ }
+
+ } while (ret == Q_FULL);
+ }
+ chSysUnlock();
+}
diff --git a/firmware/application/i2c_device_to_host.h b/firmware/application/i2c_device_to_host.h
new file mode 100644
index 00000000..61827106
--- /dev/null
+++ b/firmware/application/i2c_device_to_host.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 Bernd Herzog
+ * Copyright (C) 2024 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 __I2C_DEVICE_TO_HOST_H
+#define __I2C_DEVICE_TO_HOST_H
+
+#include "ch.h"
+#include "hal.h"
+
+#ifndef I2CSHELL_BUFFERS_SIZE
+#define I2CSHELL_BUFFERS_SIZE 64
+#endif
+
+struct I2CShellDriverVMT {
+ _base_asynchronous_channel_methods
+};
+
+struct I2CShellDriver {
+ /** @brief Virtual Methods Table.*/
+ const struct I2CShellDriverVMT* vmt;
+ InputQueue iqueue; /* Output queue.*/
+ OutputQueue oqueue; /* Input circular buffer.*/
+ uint8_t ib[I2CSHELL_BUFFERS_SIZE]; /* Output circular buffer.*/
+ uint8_t ob[I2CSHELL_BUFFERS_SIZE];
+};
+
+typedef struct I2CShellDriver I2CShellDriver;
+
+extern I2CShellDriver I2CD1;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void init_i2c_shell_driver(I2CShellDriver* sdp);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/firmware/application/io.hpp b/firmware/application/io.hpp
index ebbf9c45..fcaad0e9 100644
--- a/firmware/application/io.hpp
+++ b/firmware/application/io.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __IO_H
+#define __IO_H
#include "file.hpp"
@@ -38,3 +39,5 @@ class Writer {
};
} /* namespace stream */
+
+#endif
diff --git a/firmware/application/io_convert.hpp b/firmware/application/io_convert.hpp
index 78014203..0455444f 100644
--- a/firmware/application/io_convert.hpp
+++ b/firmware/application/io_convert.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __IO_CONVERT_H
+#define __IO_CONVERT_H
#include "io_file.hpp"
@@ -77,3 +78,5 @@ class FileConvertWriter : public stream::Writer {
File file_{};
uint64_t bytes_written_{0};
};
+
+#endif
diff --git a/firmware/application/io_file.hpp b/firmware/application/io_file.hpp
index 5dffa789..c7988ff4 100644
--- a/firmware/application/io_file.hpp
+++ b/firmware/application/io_file.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __IO_FILE_H
+#define __IO_FILE_H
#include "io.hpp"
@@ -71,3 +72,5 @@ class FileWriter : public stream::Writer {
};
using RawFileWriter = FileWriter;
+
+#endif
diff --git a/firmware/application/io_wave.hpp b/firmware/application/io_wave.hpp
index 0293e5f9..6d32454f 100644
--- a/firmware/application/io_wave.hpp
+++ b/firmware/application/io_wave.hpp
@@ -20,7 +20,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __IO_WAVE_H
+#define __IO_WAVE_H
#include "io_file.hpp"
@@ -181,3 +182,5 @@ class WAVFileWriter : public FileWriter {
Optional update_header();
Optional write_tags();
};
+
+#endif
diff --git a/firmware/application/irq_controls.cpp b/firmware/application/irq_controls.cpp
index 06a1d21f..f9b53321 100644
--- a/firmware/application/irq_controls.cpp
+++ b/firmware/application/irq_controls.cpp
@@ -85,7 +85,7 @@ static bool touch_update() {
case IO::TouchPinsConfig::SensePressure: {
const auto z1 = samples.xp - samples.xn;
const auto z2 = samples.yp - samples.yn;
- const auto touch_raw = (z1 > touch::touch_threshold) || (z2 > touch::touch_threshold);
+ const auto touch_raw = (z1 > portapack::touch_threshold) || (z2 > portapack::touch_threshold);
touch_debounce = (touch_debounce << 1) | (touch_raw ? 1U : 0U);
touch_detected = ((touch_debounce & touch_debounce_mask) == touch_debounce_mask);
if (!touch_detected && !touch_cycle) {
diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp
index b0f56fec..c1ca5b5c 100755
--- a/firmware/application/main.cpp
+++ b/firmware/application/main.cpp
@@ -96,7 +96,6 @@ Continuous (Fox-oring)
// Multimon-style stuff:
// TODO: DMR detector
// TODO: GSM channel detector
-// TODO: Playdead amnesia and login
// TODO: Setup: Play dead by default ? Enable/disable ?
// Old or low-priority stuff:
@@ -106,7 +105,6 @@ Continuous (Fox-oring)
// TODO: Check more OOK encoders
// BUG (fixed ?): No audio in about when shown second time
// TODO: Show MD5 mismatches for modules not found, etc...
-// TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule
// BUG: Description doesn't show up first time going to system>module info (UI drawn on top)
// TODO: Two players tic-tac-toe
// TODO: Analog TV pong game
@@ -141,6 +139,7 @@ Continuous (Fox-oring)
#include "sd_card.hpp"
#include
+#include "i2cdevmanager.hpp"
#include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */
rffc507x::RFFC507x first_if;
@@ -161,6 +160,7 @@ static void event_loop() {
event_dispatcher.set_display_sleep(true);
}};
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
+ i2cdev::I2CDevManager::setEventDispatcher(&event_dispatcher);
system_view.get_navigation_view()->handle_autostart();
event_dispatcher.run();
}
diff --git a/firmware/application/ook_file.cpp b/firmware/application/ook_file.cpp
new file mode 100644
index 00000000..a3515fe9
--- /dev/null
+++ b/firmware/application/ook_file.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 gullradriel
+ *
+ * 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 "ook_file.hpp"
+
+using namespace portapack;
+namespace fs = std::filesystem;
+
+/*
+ struct of an OOK file:
+
+ Frequency SampleRate SymbolRate Repeat PauseSymbolDuration Payload
+
+ -Frequency is in hertz
+ -SampleRate is one of 250k, 1M, 2M, 5M , 10M ,20M
+ -SymbolRate is the number of symbols /s
+ -Repeat is the number of times we will repeat the payload
+ -PauseSymbolDuration is the duration of the pause between repeat, in usec
+ -Payload is the payload in form of a string of 0 and 1
+*/
+
+bool read_ook_file(const std::filesystem::path& path, ook_file_data& ook_data) {
+ File data_file;
+ auto open_result = data_file.open(path);
+ if (open_result) {
+ return false;
+ }
+
+ FileLineReader reader(data_file);
+
+ for (const auto& line : reader) {
+ // Split the line into segments to extract each value
+ size_t first_space = line.find(' ');
+ size_t second_space = line.find(' ', first_space + 1);
+ size_t third_space = line.find(' ', second_space + 1);
+ size_t fourth_space = line.find(' ', third_space + 1);
+ size_t fifth_space = line.find(' ', fourth_space + 1);
+
+ // Extract each component of the line
+ std::string frequency_str = line.substr(0, first_space);
+ std::string sample_rate_str = line.substr(first_space + 1, second_space - first_space - 1);
+ std::string symbol_rate_str = line.substr(second_space + 1, third_space - second_space - 1);
+ std::string repeat_str = line.substr(third_space + 1, fourth_space - third_space - 1);
+ std::string pause_symbol_duration_str = line.substr(fourth_space + 1, fifth_space - fourth_space - 1);
+ std::string payload_data = line.substr(fifth_space + 1); // Extract binary payload as final value
+
+ // Convert and assign frequency
+ ook_data.frequency = std::stoull(frequency_str);
+ // Convert and assign symbol_rate
+ ook_data.symbol_rate = static_cast(atoi(symbol_rate_str.c_str()));
+ // Convert and assign repeat count
+ ook_data.repeat = static_cast(atoi(repeat_str.c_str()));
+ // Convert and assign pause_symbol_duration
+ ook_data.pause_symbol_duration = static_cast(atoi(pause_symbol_duration_str.c_str()));
+ // Select sample rate based on value read from file
+ if (sample_rate_str == "250k") {
+ ook_data.sample_rate = 250000U;
+ } else if (sample_rate_str == "1M") {
+ ook_data.sample_rate = 1000000U;
+ } else if (sample_rate_str == "2M") {
+ ook_data.sample_rate = 2000000U;
+ } else if (sample_rate_str == "5M") {
+ ook_data.sample_rate = 5000000U;
+ } else if (sample_rate_str == "10M") {
+ ook_data.sample_rate = 10000000U;
+ } else if (sample_rate_str == "20M") {
+ ook_data.sample_rate = 20000000U;
+ } else {
+ return false;
+ }
+ // Update payload with binary data
+ ook_data.payload = std::move(payload_data);
+ // Process only the first line
+ break;
+ }
+ return true;
+}
+
+bool save_ook_file(ook_file_data& ook_data, const std::filesystem::path& path) {
+ // delete file if it exists
+ delete_file(path);
+
+ // Attempt to open, if it can't be opened. Create new.
+ auto src = std::make_unique();
+ auto error = src->open(path, false, true);
+ if (error) {
+ return false;
+ }
+
+ std::string sample_rate_str;
+ if (ook_data.sample_rate == 250000U) {
+ sample_rate_str = "250k";
+ } else if (ook_data.sample_rate == 1000000U) {
+ sample_rate_str = "1M";
+ } else if (ook_data.sample_rate == 2000000U) {
+ sample_rate_str = "2M";
+ } else if (ook_data.sample_rate == 5000000U) {
+ sample_rate_str = "5M";
+ } else if (ook_data.sample_rate == 10000000U) {
+ sample_rate_str = "10M";
+ } else if (ook_data.sample_rate == 20000000U) {
+ sample_rate_str = "20M";
+ } else {
+ return false;
+ }
+
+ // write informations
+ src->write_line(to_string_dec_uint(ook_data.frequency) + " " +
+ sample_rate_str + " " +
+ to_string_dec_uint(ook_data.symbol_rate) + " " +
+ to_string_dec_uint(ook_data.repeat) + " " +
+ to_string_dec_uint(ook_data.pause_symbol_duration) + " " +
+ ook_data.payload);
+
+ // Close files
+ src.reset();
+
+ return true;
+}
+
+void start_ook_file_tx(ook_file_data& ook_data) {
+ size_t bitstream_length = encoders::make_bitstream(const_cast(ook_data.payload)); // Convert the message into a bitstream
+
+ transmitter_model.set_target_frequency(ook_data.frequency); // Set target frequency
+ transmitter_model.set_sampling_rate(ook_data.sample_rate); // Set the OOK sampling rate
+ transmitter_model.set_baseband_bandwidth(1750000); // Set the baseband bandwidth
+ transmitter_model.enable(); // Enable the transmitter
+
+ // Configure OOK data and transmission characteristics
+ baseband::set_ook_data(
+ bitstream_length, // Length of the bitstream to transmit
+ ook_data.sample_rate / ook_data.symbol_rate, // Calculate symbol period based on repetition rate
+ ook_data.repeat, // Set the number of times the whole bitstream is repeated
+ ook_data.pause_symbol_duration // Set the pause_symbol between reps
+ );
+}
+
+void stop_ook_file_tx() {
+ transmitter_model.disable(); // Disable the transmitter
+}
diff --git a/firmware/application/ook_file.hpp b/firmware/application/ook_file.hpp
new file mode 100644
index 00000000..03ddaace
--- /dev/null
+++ b/firmware/application/ook_file.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 gullradriel
+ *
+ * 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 __OOK_FILE_HPP__
+#define __OOK_FILE_HPP__
+
+#include "portapack.hpp"
+#include "metadata_file.hpp"
+#include "encoders.hpp" // Includes data encoding functions for transmission
+#include "convert.hpp"
+#include "file_reader.hpp"
+#include "string_format.hpp"
+#include
+#include "transmitter_model.hpp"
+#include "baseband_api.hpp" // Includes baseband API for handling transmission settings
+
+struct ook_file_data {
+ rf::Frequency frequency = rf::Frequency(0); // Default frequency
+ uint32_t sample_rate = 0; // Default sample rate
+ uint16_t symbol_rate = 0; // Default bit duration
+ uint16_t pause_symbol_duration = 0; // Default pause between repeat
+ uint16_t repeat = 0; // Default repeat
+ std::string payload = ""; // Default payload
+};
+
+bool read_ook_file(const std::filesystem::path& path, ook_file_data& ook_data);
+bool save_ook_file(ook_file_data& data, const std::filesystem::path& path);
+void start_ook_file_tx(ook_file_data& ook_data);
+void stop_ook_file_tx();
+
+#endif // __FLIPPER_SUBFILE_HPP__
diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp
index 7520a7c7..251a265a 100644
--- a/firmware/application/portapack.cpp
+++ b/firmware/application/portapack.cpp
@@ -53,6 +53,8 @@ using asahi_kasei::ak4951::AK4951;
#include "string_format.hpp"
#include "bitmap.hpp"
#include "ui_widget.hpp"
+#include "i2cdevmanager.hpp"
+#include "battery.hpp"
namespace portapack {
@@ -93,6 +95,7 @@ TemperatureLogger temperature_logger;
bool antenna_bias{false};
uint32_t bl_tick_counter{0};
+uint16_t touch_threshold{32};
void set_antenna_bias(const bool v) {
antenna_bias = v;
@@ -536,6 +539,11 @@ 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
+ */
+ touch_threshold = portapack::persistent_memory::touchscreen_threshold();
if (lcd_fast_setup)
draw_splash_screen_icon(0, ui::bitmap_icon_memory);
@@ -588,7 +596,8 @@ init_status_t init() {
chThdSleepMilliseconds(10);
audio::init(portapack_audio_codec());
- battery::BatteryManagement::init(persistent_memory::ui_override_batt_calc());
+ battery::BatteryManagement::set_calc_override(persistent_memory::ui_override_batt_calc());
+ i2cdev::I2CDevManager::init();
if (lcd_fast_setup)
draw_splash_screen_icon(4, ui::bitmap_icon_speaker);
diff --git a/firmware/application/portapack.hpp b/firmware/application/portapack.hpp
index 7a446ed4..a13b987b 100644
--- a/firmware/application/portapack.hpp
+++ b/firmware/application/portapack.hpp
@@ -19,7 +19,8 @@
* Boston, MA 02110-1301, USA.
*/
-#pragma once
+#ifndef __PORTAPACK_H
+#define __PORTAPACK_H
#include "portapack_io.hpp"
@@ -33,13 +34,9 @@
#include "backlight.hpp"
#include "usb_serial.hpp"
-#include "ads1110.hpp"
-#include "max17055.hpp"
-
#include "radio.hpp"
#include "clock_manager.hpp"
#include "temperature_logger.hpp"
-#include "battery.hpp"
#include "theme.hpp"
/* TODO: This would be better as a class to add
@@ -71,6 +68,7 @@ extern TransmitterModel transmitter_model;
extern uint32_t bl_tick_counter;
extern bool antenna_bias;
+extern uint16_t touch_threshold;
extern TemperatureLogger temperature_logger;
@@ -89,3 +87,5 @@ Backlight* backlight();
extern bool async_tx_enabled; // this is for serial tx things, globally
} /* namespace portapack */
+
+#endif
diff --git a/firmware/application/tone_key.cpp b/firmware/application/tone_key.cpp
index 41e3aec2..291a42c0 100644
--- a/firmware/application/tone_key.cpp
+++ b/firmware/application/tone_key.cpp
@@ -96,7 +96,7 @@ float tone_key_frequency(tone_index index) {
std::string tone_key_string(tone_index index) {
if (index < 0 || (unsigned)index >= tone_keys.size())
return std::string("");
- return tone_keys[index].first;
+ return (std::string)tone_keys[index].first;
}
// Return string showing frequency only from specific table index
diff --git a/firmware/application/tone_key.hpp b/firmware/application/tone_key.hpp
index 13eae1b4..0f57e90f 100644
--- a/firmware/application/tone_key.hpp
+++ b/firmware/application/tone_key.hpp
@@ -26,6 +26,7 @@
#include
#include
+#include
#include
namespace tonekey {
@@ -35,7 +36,7 @@ namespace tonekey {
#define F2Ix100(x) (int32_t)(x * 100.0 + 0.5) // add 0.5f to round vs truncate during FP->int conversion
using tone_index = int32_t;
-using tone_key_t = std::vector>;
+using tone_key_t = std::vector>;
extern const tone_key_t tone_keys;
diff --git a/firmware/application/touch.hpp b/firmware/application/touch.hpp
index 2594d40a..91758dfc 100644
--- a/firmware/application/touch.hpp
+++ b/firmware/application/touch.hpp
@@ -36,14 +36,6 @@ namespace touch {
using sample_t = uint16_t;
-constexpr sample_t sample_max = 1023;
-
-// If you have a dead bottom-left corner try to increase the sensitivity,
-// but look for flickering touch indicator in the Buttons test screen
-// in which case decrease sensitivity to avoid killing backlight timeout
-constexpr sample_t touch_sensitivity = 32;
-constexpr sample_t touch_threshold = sample_max / touch_sensitivity;
-
struct Samples {
sample_t xp;
sample_t xn;
diff --git a/firmware/application/ui/ui_btngrid.cpp b/firmware/application/ui/ui_btngrid.cpp
index 8c6b60be..d9646c6a 100644
--- a/firmware/application/ui/ui_btngrid.cpp
+++ b/firmware/application/ui/ui_btngrid.cpp
@@ -118,30 +118,36 @@ void BtnGridView::clear() {
menu_item_views.clear();
}
-void BtnGridView::add_items(std::initializer_list new_items) {
+void BtnGridView::add_items(std::initializer_list new_items, bool inhibit_update) {
for (auto item : new_items) {
if (!blacklisted_app(item))
menu_items.push_back(item);
}
- update_items();
-}
-
-void BtnGridView::add_item(GridItem new_item) {
- if (!blacklisted_app(new_item)) {
- menu_items.push_back(new_item);
+ if (!inhibit_update) {
update_items();
}
}
-void BtnGridView::insert_item(GridItem new_item, uint8_t position) {
+void BtnGridView::add_item(const GridItem& new_item, bool inhibit_update) {
+ if (!blacklisted_app(new_item)) {
+ menu_items.push_back(new_item);
+ if (!inhibit_update) {
+ update_items();
+ }
+ }
+}
+
+void BtnGridView::insert_item(const GridItem& new_item, size_t position, bool inhibit_update) {
if (!blacklisted_app(new_item)) {
if (position < menu_items.size()) {
auto pos_iter = menu_items.begin() + position;
menu_items.insert(pos_iter, new_item);
- update_items();
} else {
menu_items.push_back(new_item);
+ }
+
+ if (!inhibit_update) {
update_items();
}
}
diff --git a/firmware/application/ui/ui_btngrid.hpp b/firmware/application/ui/ui_btngrid.hpp
index b20a3433..e862b2c7 100644
--- a/firmware/application/ui/ui_btngrid.hpp
+++ b/firmware/application/ui/ui_btngrid.hpp
@@ -59,9 +59,9 @@ class BtnGridView : public View {
~BtnGridView();
- void add_items(std::initializer_list new_items);
- void add_item(GridItem new_item);
- void insert_item(GridItem new_item, uint8_t position);
+ void add_items(std::initializer_list new_items, bool inhibit_update = false);
+ void add_item(const GridItem& new_item, bool inhibit_update = false);
+ void insert_item(const GridItem& new_item, size_t position, bool inhibit_update = false);
void set_max_rows(int rows);
int rows();
void clear();
@@ -81,12 +81,13 @@ class BtnGridView : public View {
bool on_encoder(const EncoderEvent event) override;
bool blacklisted_app(GridItem new_item);
+ void update_items();
+
protected:
virtual void on_populate() = 0;
private:
int rows_{3};
- void update_items();
void on_tick_second();
bool keep_highlight{false};
diff --git a/firmware/application/ui/ui_geomap.hpp b/firmware/application/ui/ui_geomap.hpp
index 8133d115..772efeda 100644
--- a/firmware/application/ui/ui_geomap.hpp
+++ b/firmware/application/ui/ui_geomap.hpp
@@ -344,7 +344,7 @@ class GeoMapView : public View {
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
Button button_ok{
- {20 * 8, 8, 8 * 8, 2 * 16},
+ {screen_width - 15 * 8, 0, 15 * 8, 1 * 16},
"OK"};
};
diff --git a/firmware/application/ui/ui_rssi.cpp b/firmware/application/ui/ui_rssi.cpp
index 4d02aab7..de0018f6 100644
--- a/firmware/application/ui/ui_rssi.cpp
+++ b/firmware/application/ui/ui_rssi.cpp
@@ -100,8 +100,19 @@ void RSSI::paint(Painter& painter) {
const Rect r5{r.left() + peak - 3, r.top(), 3, r.height()};
painter.fill_rectangle(
r5,
- Theme::getInstance()->fg_green->foreground);
+ Theme::getInstance()->fg_orange->foreground);
}
+
+ // dB - x
+ constexpr int db_min = -80;
+ constexpr int db_max = 10;
+ constexpr int db_delta = db_max - db_min;
+ const range_t x_db_range{0, r.width() - 1};
+ const int16_t x_db = x_db_range.clip((db_ - db_min) * r.width() / db_delta);
+
+ const Rect r_db{r.left() + x_db, r.top(), 1, r.height()};
+
+ if (db_) painter.fill_rectangle(r_db, Color::green());
} else {
// vertical bottom to top level meters
const range_t y_avg_range{0, r.height() - 1};
@@ -115,7 +126,7 @@ void RSSI::paint(Painter& painter) {
// y_min
const Rect r0{r.left(), r.bottom() - y_min, r.width(), y_min};
- painter.fill_rectangle(
+ painter.fill_rectangle( // TODO: the blue plot is broken in vertical bars, not from the dB PR (#2403)
r0,
Color::blue());
@@ -149,8 +160,18 @@ void RSSI::paint(Painter& painter) {
const Rect r5{r.left(), r.bottom() - peak - 3, r.width(), 3};
painter.fill_rectangle(
r5,
- Color::green());
+ Color::orange());
}
+
+ // dB - y
+ constexpr int db_min = -80;
+ constexpr int db_max = 10;
+ constexpr int db_delta = db_max - db_min;
+ const range_t y_db_range{0, r.height() - 1};
+ const int16_t y_db = y_db_range.clip((db_ - db_min) * r.height() / db_delta);
+
+ const Rect r_db{r.left(), r.bottom() - y_db, r.width(), 3};
+ if (db_) painter.fill_rectangle(r_db, Color::green());
}
if (pitch_rssi_enabled) {
baseband::set_pitch_rssi((avg_ - raw_min) * 2000 / raw_delta, true);
@@ -159,7 +180,7 @@ void RSSI::paint(Painter& painter) {
const Rect r6{r.left(), r.top(), r.width(), r.height()};
painter.draw_rectangle(
r6,
- Color::white());
+ Color::white()); // TODO this and all the following Color struct call should satisfy the new "theme" system ref
}
}
@@ -500,4 +521,8 @@ bool RSSI::on_touch(const TouchEvent event) {
return false;
}
}
+
+void RSSI::set_db(int16_t db) {
+ db_ = db;
+}
} /* namespace ui */
diff --git a/firmware/application/ui/ui_rssi.hpp b/firmware/application/ui/ui_rssi.hpp
index 7af30a3c..ade17a15 100644
--- a/firmware/application/ui/ui_rssi.hpp
+++ b/firmware/application/ui/ui_rssi.hpp
@@ -61,6 +61,7 @@ class RSSI : public Widget {
void on_focus() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
+ void set_db(int16_t db);
private:
int8_t min_ = 0;
@@ -68,6 +69,7 @@ class RSSI : public Widget {
int8_t max_ = 0;
int8_t peak_ = 0;
size_t peak_duration_ = 0;
+ int16_t db_ = 0;
bool instant_exec_{false};
bool pitch_rssi_enabled = false;
diff --git a/firmware/application/ui/ui_tone_key.cpp b/firmware/application/ui/ui_tone_key.cpp
index 019be359..814eaa35 100644
--- a/firmware/application/ui/ui_tone_key.cpp
+++ b/firmware/application/ui/ui_tone_key.cpp
@@ -34,7 +34,7 @@ void tone_keys_populate(OptionsField& field) {
for (size_t c = 0; c < tone_keys.size(); c++) {
auto f = tone_keys[c].second;
if ((c != 0) && (f < 1000 * 100))
- tone_name = "CTCSS " + fx100_string(f) + " #" + tone_keys[c].first;
+ tone_name = "CTCSS " + fx100_string(f) + " #" + (std::string)tone_keys[c].first;
else
tone_name = tone_keys[c].first;
diff --git a/firmware/application/ui_external_items_menu_loader.cpp b/firmware/application/ui_external_items_menu_loader.cpp
index b107bc1c..6ec1202b 100644
--- a/firmware/application/ui_external_items_menu_loader.cpp
+++ b/firmware/application/ui_external_items_menu_loader.cpp
@@ -4,14 +4,39 @@
#include "file_path.hpp"
#include "ui_standalone_view.hpp"
+#include "i2cdevmanager.hpp"
+#include "i2cdev_ppmod.hpp"
+
namespace ui {
/* static */ std::vector> ExternalItemsMenuLoader::bitmaps;
-// iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info.
-/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function callback) {
+// iterates over all possible ext apps-s, and if it is runnable on the current system, it'll call the callback, and pass minimal info. used to print to console, and for autostart setting's app list. where the minimal info is enough
+// please keep in sync with load_external_items
+/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function callback, bool module_included) {
if (!callback) return;
+ auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
+
+ if (dev && module_included) {
+ auto device_info = dev->readDeviceInfo();
+
+ if (device_info.has_value()) {
+ for (uint32_t i = 0; i < device_info->application_count; i++) {
+ auto appInfo = dev->getStandaloneAppInfo(i);
+ if (appInfo.has_value() == false) {
+ continue;
+ }
+
+ if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
+ continue;
+
+ AppInfoConsole appInfoConsole = {reinterpret_cast(&appInfo->app_name[0]), reinterpret_cast(&appInfo->app_name[0]), appInfo->menu_location};
+ callback(appInfoConsole);
+ }
+ }
+ }
+
if (sd_card::status() != sd_card::Status::Mounted)
return;
@@ -33,18 +58,16 @@ namespace ui {
continue;
bool versionMatches = VERSION_MD5 == application_information.app_version;
- if (!versionMatches) continue;
- // here the app is startable and good.
- std::string appshortname = filePath.filename().string();
- if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
- // Remove the ".ppma" suffix
- appshortname = appshortname.substr(0, appshortname.size() - 5);
+
+ if (versionMatches) {
+ std::string appshortname = filePath.filename().string();
+ if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
+ // Remove the ".ppma" suffix
+ appshortname = appshortname.substr(0, appshortname.size() - 5);
+ }
+ AppInfoConsole appInfoConsole = {appshortname.c_str(), reinterpret_cast(&application_information.app_name[0]), application_information.menu_location};
+ callback(appInfoConsole);
}
- AppInfoConsole info{
- .appCallName = appshortname.c_str(),
- .appFriendlyName = reinterpret_cast(&application_information.app_name[0]),
- .appLocation = application_information.menu_location};
- callback(info);
}
for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) {
@@ -61,28 +84,78 @@ namespace ui {
if (!readResult)
continue;
- if (application_information.header_version < CURRENT_STANDALONE_APPLICATION_API_VERSION)
+ if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
continue;
- // here the app is startable and good.
std::string appshortname = filePath.filename().string();
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppmp") {
// Remove the ".ppmp" suffix
appshortname = appshortname.substr(0, appshortname.size() - 5);
}
- AppInfoConsole info{
- .appCallName = appshortname.c_str(),
- .appFriendlyName = reinterpret_cast(&application_information.app_name[0]),
- .appLocation = application_information.menu_location};
-
- callback(info);
+ AppInfoConsole appInfoConsole = {appshortname.c_str(), reinterpret_cast(&application_information.app_name[0]), application_information.menu_location};
+ callback(appInfoConsole);
}
}
-/* static */ std::vector ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
+/* static */ std::vector ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
bitmaps.clear();
- std::vector external_apps;
+ std::vector external_apps;
+
+ auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
+
+ if (dev) {
+ auto device_info = dev->readDeviceInfo();
+
+ if (device_info.has_value()) {
+ for (uint32_t i = 0; i < device_info->application_count; i++) {
+ auto appInfo = dev->getStandaloneAppInfo(i);
+ if (appInfo.has_value() == false) {
+ continue;
+ }
+
+ if (appInfo->menu_location != app_location) {
+ continue;
+ }
+
+ if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
+ continue;
+
+ GridItemEx gridItem = {};
+ gridItem.text = reinterpret_cast(&appInfo->app_name[0]);
+
+ gridItem.color = Color((uint16_t)appInfo->icon_color);
+
+ auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data};
+ gridItem.bitmap = dyn_bmp.bitmap();
+ bitmaps.push_back(std::move(dyn_bmp));
+
+ gridItem.on_select = [&nav, appInfo, i]() {
+ auto dev2 = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
+ if (dev2) {
+ auto app_image = reinterpret_cast(portapack::memory::map::m4_code.end() - appInfo->binary_size);
+ for (size_t j = 0; j < appInfo->binary_size; j += 128) {
+ auto segment = dev2->downloadStandaloneApp(i, j);
+ if (segment.size() != 128) {
+ continue;
+ }
+
+ std::copy(segment.begin(), segment.end(), app_image + j);
+ }
+
+ if (!run_module_app(nav, app_image, appInfo->binary_size)) {
+ nav.display_modal("Error", "Unable to run downloaded app.");
+ }
+ } else
+ nav.display_modal("Error", "Unable to download app.");
+ };
+
+ gridItem.desired_position = -1; // TODO: Where should we put the module's app icon? First? Last? Also configurable?
+
+ external_apps.push_back(gridItem);
+ }
+ }
+ }
if (sd_card::status() != sd_card::Status::Mounted)
return external_apps;
@@ -109,7 +182,7 @@ namespace ui {
bool versionMatches = VERSION_MD5 == application_information.app_version;
- GridItem gridItem = {};
+ GridItemEx gridItem = {};
gridItem.text = reinterpret_cast(&application_information.app_name[0]);
if (versionMatches) {
@@ -124,6 +197,8 @@ namespace ui {
nav.display_modal("Error", "The .ppma file in your " + apps_dir.string() + "\nfolder can't be read. Please\nupdate your SD Card content.");
}
};
+
+ gridItem.desired_position = application_information.desired_menu_position;
} else {
gridItem.color = Theme::getInstance()->fg_light->foreground;
@@ -132,6 +207,8 @@ namespace ui {
gridItem.on_select = [&nav]() {
nav.display_modal("Error", "The .ppma file in your " + apps_dir.string() + "\nfolder is outdated. Please\nupdate your SD Card content.");
};
+
+ gridItem.desired_position = application_information.desired_menu_position;
}
external_apps.push_back(gridItem);
@@ -157,7 +234,7 @@ namespace ui {
if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
continue;
- GridItem gridItem = {};
+ GridItemEx gridItem = {};
gridItem.text = reinterpret_cast(&application_information.app_name[0]);
gridItem.color = Color((uint16_t)application_information.icon_color);
@@ -172,6 +249,8 @@ namespace ui {
}
};
+ gridItem.desired_position = -1; // No desired position support for standalone apps yet
+
external_apps.push_back(gridItem);
}
@@ -259,6 +338,7 @@ namespace ui {
return true;
}
+// TODO: implement baseband image support
/* static */ bool ExternalItemsMenuLoader::run_standalone_app(ui::NavigationView& nav, std::filesystem::path filePath) {
File app;
@@ -266,7 +346,8 @@ namespace ui {
if (openError)
return false;
- auto app_image = std::make_unique(app.size());
+ // TODO: move this to m4 memory space
+ auto app_image = reinterpret_cast(portapack::memory::map::m4_code.end() - app.size());
// read file in 512 byte chunks
for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) {
@@ -286,11 +367,25 @@ namespace ui {
uint32_t* ptr = reinterpret_cast(&app_image[file_read_index * 4]);
if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
- *ptr = *ptr - 0xADB10000 + (uint32_t)app_image.get();
+ *ptr = *ptr - 0xADB10000 + (uint32_t)app_image;
}
}
- nav.push(std::move(app_image));
+ nav.push(app_image);
+ return true;
+}
+
+// TODO: implement baseband image support
+/* static */ bool ExternalItemsMenuLoader::run_module_app(ui::NavigationView& nav, uint8_t* app_image, size_t app_size) {
+ for (size_t file_read_index = 0; file_read_index < app_size / 4; file_read_index++) {
+ uint32_t* ptr = reinterpret_cast(&app_image[file_read_index * 4]);
+
+ if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
+ *ptr = *ptr - 0xADB10000 + (uint32_t)app_image;
+ }
+ }
+
+ nav.push(app_image);
return true;
}
diff --git a/firmware/application/ui_external_items_menu_loader.hpp b/firmware/application/ui_external_items_menu_loader.hpp
index 74745d17..59695609 100644
--- a/firmware/application/ui_external_items_menu_loader.hpp
+++ b/firmware/application/ui_external_items_menu_loader.hpp
@@ -55,11 +55,16 @@ class DynamicBitmap {
class ExternalItemsMenuLoader {
public:
- static std::vector load_external_items(app_location_t, NavigationView&);
+ struct GridItemEx : GridItem {
+ int32_t desired_position;
+ };
+
+ static std::vector load_external_items(app_location_t, NavigationView&);
ExternalItemsMenuLoader() = delete;
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
- static void load_all_external_items_callback(std::function callback);
+ static bool run_module_app(ui::NavigationView&, uint8_t*, size_t);
+ static void load_all_external_items_callback(std::function callback, bool module_included = false);
private:
static std::vector> bitmaps;
diff --git a/firmware/application/ui_handwrite.cpp b/firmware/application/ui_handwrite.cpp
deleted file mode 100644
index 4ef103bd..00000000
--- a/firmware/application/ui_handwrite.cpp
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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_handwrite.hpp"
-
-#include "portapack.hpp"
-#include "hackrf_hal.hpp"
-#include "portapack_shared_memory.hpp"
-
-#include
-
-using namespace portapack;
-
-namespace ui {
-
-void HandWriteView::paint(Painter& painter) {
- _painter = &painter;
-}
-
-HandWriteView::HandWriteView(
- NavigationView& nav,
- std::string* str,
- size_t max_length)
- : TextEntryView(nav, str, max_length) {
- size_t n;
-
- // Handwriting alphabet definition here
- handwriting = &handwriting_unistroke;
-
- add_children({&button_case});
-
- const auto button_fn = [this](Button& button) {
- this->on_button(button);
- };
-
- n = 0;
- for (auto& button : num_buttons) {
- add_child(&button);
- button.on_select = button_fn;
- button.set_parent_rect({static_cast(n * 24),
- static_cast(236),
- 24, 28});
- const std::string label{
- (char)(n + '0')};
- button.set_text(label);
- button.id = n + '0';
- n++;
- }
-
- n = 0;
- for (auto& button : special_buttons) {
- add_child(&button);
- button.on_select = button_fn;
- button.set_parent_rect({static_cast(50 + n * 24),
- static_cast(270),
- 24, 28});
- const std::string label{
- (char)(special_chars[n])};
- button.set_text(label);
- button.id = special_chars[n];
- n++;
- }
-
- button_case.on_select = [this, &nav](Button&) {
- if (_lowercase == true) {
- _lowercase = false;
- button_case.set_text("LC");
- } else {
- _lowercase = true;
- button_case.set_text("UC");
- }
- };
-
- button_ok.on_select = [this, &nav](Button&) {
- if (on_changed)
- on_changed(_str);
- nav.pop();
- };
-
- update_text();
-}
-
-bool HandWriteView::on_touch(const TouchEvent event) {
- if (event.type == ui::TouchEvent::Type::Start) {
- stroke_index = 0;
- move_wait = 3;
- tracing = true;
- }
- if (event.type == ui::TouchEvent::Type::End) {
- tracing = false;
- guess_letter();
- }
- if (event.type == ui::TouchEvent::Type::Move) {
- if (tracing)
- current_pos = event.point;
- }
- return true;
-}
-
-void HandWriteView::clear_zone(const Color color, const bool flash) {
- display.fill_rectangle(
- {{0, 32}, {240, 216}},
- color);
- if (flash) {
- flash_timer = 8;
- } else {
- // Draw grid
- _painter->draw_rectangle(
- {{0, 32}, {80, 216}},
- Color::grey());
- _painter->draw_rectangle(
- {{80, 32}, {80, 216}},
- Color::grey());
- _painter->draw_rectangle(
- {{160, 32}, {80, 216}},
- Color::grey());
- _painter->draw_rectangle(
- {{0, 104}, {240, 72}},
- Color::grey());
- }
-}
-
-void HandWriteView::guess_letter() {
- uint32_t symbol, match, count, stroke_idx, stroke_data;
- Condition cond;
- Direction dir;
- bool matched;
-
- // Letter guessing
- if (stroke_index) {
- for (symbol = 0; symbol < handwriting->letter_count; symbol++) {
- count = handwriting->letter[symbol].count;
- matched = false;
- if (count) {
- // We have a count match to do
- if ((count == 1) && (stroke_index == 1)) matched = true;
- if ((count == 2) && (stroke_index == 2)) matched = true;
- if ((count == 3) && (stroke_index > 2)) matched = true;
- } else {
- matched = true;
- }
- if (matched) {
- for (match = 0; match < 3; match++) {
- cond = handwriting->letter[symbol].match[match].cond;
- dir = handwriting->letter[symbol].match[match].dir;
- if ((cond != cond_empty) && (dir != dir_empty)) {
- if (cond == last) {
- if (stroke_index)
- stroke_idx = stroke_index - 1;
- else
- stroke_idx = 0;
- } else if (cond == stroke_a)
- stroke_idx = 0;
- else if (cond == stroke_b)
- stroke_idx = 1;
- else if (cond == stroke_c)
- stroke_idx = 2;
- else
- stroke_idx = 3;
- if (stroke_idx >= stroke_index) break;
- stroke_data = stroke_list[stroke_idx];
- if ((dir & 0xF0) == 0xF0) {
- if ((dir & 0x0F) != (stroke_data & 0x0F)) break;
- } else if ((dir & 0x0F) == 0x0F) {
- if ((dir & 0xF0) != (stroke_data & 0xF0)) break;
- } else {
- if (dir != (int32_t)stroke_data) break;
- }
- }
- }
- if (match == 3)
- break;
- else
- matched = false;
- }
- }
- if (matched) {
- if (symbol) {
- if (_lowercase)
- char_add('a' + symbol - 1);
- else
- char_add('A' + symbol - 1);
- clear_zone(Color::green(), true); // Green flash
- } else {
- if (_cursor_pos) {
- char_delete();
- clear_zone(Color::yellow(), true); // Yellow flash
- } else {
- clear_zone(Color::red(), true); // Red flash
- }
- }
- } else {
- clear_zone(Color::red(), true); // Red flash
- }
- } else {
- // Short tap is space
- char_add(' ');
- clear_zone(Color::green(), true); // Green flash
- }
- update_text();
- stroke_index = 0;
-}
-
-void HandWriteView::add_stroke(uint8_t dir) {
- if (stroke_index < 8) {
- stroke_list[stroke_index] = dir;
- stroke_index++;
- } else {
- guess_letter();
- }
-}
-
-void HandWriteView::sample_pen() {
- int16_t diff_x, diff_y;
- uint8_t dir, dir_ud, dir_lr, stroke_prev;
-
- draw_cursor();
-
- if (flash_timer) {
- if (flash_timer == 1) clear_zone(Color::black(), false);
- flash_timer--;
- }
-
- if (!(sample_skip & 1)) {
- if (tracing) {
- if (move_wait) {
- move_wait--; // ~100ms delay to get rid of jitter from touch start
- } else {
- diff_x = current_pos.x() - last_pos.x();
- diff_y = current_pos.y() - last_pos.y();
-
- if (current_pos.y() <= 240) {
- display.fill_rectangle(
- {{current_pos.x(), current_pos.y()}, {4, 4}},
- Color::grey());
- }
-
- dir = 0; // UL by default
- if (abs(diff_x) > 7) {
- if (diff_x > 0)
- dir |= 0x01; // R
- } else {
- dir |= 0x02; // ?
- }
- if (abs(diff_y) > 7) {
- if (diff_y > 0)
- dir |= 0x10; // D
- } else {
- dir |= 0x20; // ?
- }
-
- // Need at least two identical directions to validate stroke
- if ((dir & 0x11) == (dir_prev & 0x11))
- dir_cnt++;
- else
- dir_cnt = 0;
- dir_prev = dir;
-
- if (dir_cnt > 1) {
- dir_cnt = 0;
- if (stroke_index) {
- if ((stroke_list[stroke_index - 1] != dir) && (dir != 0x22)) {
- // Register stroke if different from last one
- dir_ud = (dir & 0xF0);
- dir_lr = (dir & 0x0F);
- stroke_prev = stroke_list[stroke_index - 1];
- if (dir_ud == 0x20) {
- // LR changed
- if ((stroke_prev & 0x0F) != dir_lr) add_stroke(dir);
- } else if (dir_lr == 0x02) {
- // UD changed
- if ((stroke_prev & 0xF0) != dir_ud) add_stroke(dir);
- } else {
- // Add direction
- if (((stroke_prev & 0xF0) == 0x20) && (dir_ud != 0x20)) {
- // Add UD
- if ((stroke_prev & 0x0F) == dir_lr) {
- // Replace totally
- stroke_list[stroke_index - 1] = dir;
- } else if (dir_lr == 0x02) {
- // Merge UD
- stroke_list[stroke_index - 1] = dir_ud | (stroke_prev & 0x0F);
- } else {
- add_stroke(dir);
- }
- } else if (((stroke_prev & 0x0F) == 0x02) && (dir_lr != 0x02)) {
- // Add LR
- if ((stroke_prev & 0xF0) == dir_ud) {
- // Replace totally
- stroke_list[stroke_index - 1] = dir;
- } else if (dir_ud == 0x20) {
- // Merge LR
- stroke_list[stroke_index - 1] = dir_lr | (stroke_prev & 0xF0);
- } else {
- add_stroke(dir);
- }
- } else {
- add_stroke(dir);
- }
- }
- }
- } else {
- // Register first stroke
- if (dir != 0x22) add_stroke(dir);
- }
- }
- }
-
- last_pos = current_pos;
- }
- }
-
- sample_skip++;
-}
-
-void HandWriteView::on_show() {
- clear_zone(Color::black(), false);
-}
-
-void HandWriteView::on_button(Button& button) {
- char_add(button.id);
- update_text();
-}
-
-} // namespace ui
diff --git a/firmware/application/ui_handwrite.hpp b/firmware/application/ui_handwrite.hpp
deleted file mode 100644
index e039fcd8..00000000
--- a/firmware/application/ui_handwrite.hpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
- * Copyright (C) 2016 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 __UNISTROKE_H__
-#define __UNISTROKE_H__
-
-#include "ui.hpp"
-#include "ui_widget.hpp"
-#include "ui_painter.hpp"
-#include "ui_textentry.hpp"
-#include "unistroke.hpp"
-
-namespace ui {
-
-class HandWriteView : public TextEntryView {
- public:
- HandWriteView(NavigationView& nav, std::string* str, size_t max_length);
-
- HandWriteView(const HandWriteView&) = delete;
- HandWriteView(HandWriteView&&) = delete;
- HandWriteView& operator=(const HandWriteView&) = delete;
- HandWriteView& operator=(HandWriteView&&) = delete;
-
- void paint(Painter& painter) override;
- void on_show() override;
- bool on_touch(const TouchEvent event) override;
-
- private:
- const char special_chars[5] = {'\'', '.', '?', '!', '='};
-
- const HandWriting* handwriting{};
- Painter* _painter{};
- uint8_t dir_cnt{0};
- uint8_t dir_prev{0};
- uint8_t flash_timer{0};
- bool tracing{false};
- uint8_t stroke_index{0};
- uint8_t sample_skip{0}, move_wait{0};
- uint8_t stroke_list[8];
- Point start_pos{}, current_pos{}, last_pos{};
- bool _lowercase = false;
-
- void sample_pen();
- void add_stroke(uint8_t dir);
- void guess_letter();
- void clear_zone(const Color color, const bool flash);
- void on_button(Button& button);
-
- std::array