mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 10:39:30 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6e498a6d3 | |||
| ddf7f7ccb5 | |||
| fcdccdea85 | |||
| 5e55444f19 | |||
| 73f7f84718 | |||
| 7ad4ad99dd | |||
| 200f10397b | |||
| 9b352f45a2 | |||
| 51c445054e | |||
| 02586101e6 | |||
| b66d8b1aa1 | |||
| 8dc3851b55 | |||
| aef7c2be93 | |||
| 4e38dc4d3e | |||
| 520ad97f78 | |||
| 5bd208bca1 | |||
| a65ef3ce2e | |||
| b2bb37af74 | |||
| 54ad82a423 | |||
| e463405c77 | |||
| c413f925a9 | |||
| 9e1eb31150 | |||
| 7eb08f34cb | |||
| 2de1f8e6bf | |||
| 55db0e8c87 | |||
| dfa35b4290 | |||
| 0124051c4f | |||
| 65f301399f | |||
| 97889494cf | |||
| 32b35b1507 | |||
| fccff0484b | |||
| d94afe364b | |||
| 2e4db3dd27 | |||
| 97d3edbde7 | |||
| 4c18b80e42 | |||
| 01034af077 | |||
| 57ce978bab | |||
| 854ac514cb | |||
| b47a5fe0c8 | |||
| af362600ef | |||
| bad57d1391 | |||
| ef28e63a8c | |||
| eb22af84ff | |||
| 6dc25813e2 | |||
| 4ee00c0031 | |||
| 55525ed4ad | |||
| 84dcfe29a7 | |||
| 1ebe956557 | |||
| ae656f07bf | |||
| d5c894b322 | |||
| 9cea76a9f3 | |||
| ead9449609 | |||
| fff63e056a | |||
| 42da7443b4 | |||
| ee10081854 | |||
| b38d1012fa | |||
| 5075349d2d | |||
| 8497b62c13 |
+2
-1
@@ -1 +1,2 @@
|
||||
build
|
||||
*
|
||||
!firmware/tools/docker-entrypoint.sh
|
||||
@@ -1 +1 @@
|
||||
v2.0.1
|
||||
v2.0.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.2
|
||||
v2.1.0
|
||||
|
||||
@@ -61,6 +61,7 @@ cmake-build-debug/
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
|
||||
# VSCodium extensions
|
||||
.history
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://www.ebay.com/itm/116382397447) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure)*
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
|
||||
# What is this?
|
||||
|
||||
@@ -28,11 +26,11 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous [PortaPack H4M Mayhem](https://share.hackrf.app/0STXCA), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/HR60Q4) 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). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/9FJFJE), 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:
|
||||
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ FROM ubuntu:noble
|
||||
# Set location to download ARM toolkit from.
|
||||
# This will need to be changed over time or replaced with a static link to the latest release.
|
||||
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=108bd959-44bd-4619-9c19-26187abf5225&la=en&hash=E788CE92E5DFD64B2A8C246BBA91A249CB8E2D2D \
|
||||
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
|
||||
PATH=$PATH:/opt/build/armbin/bin
|
||||
|
||||
#Create volume /havoc/bin for compiled firmware binaries
|
||||
VOLUME /havoc
|
||||
@@ -15,8 +15,8 @@ RUN apt-get update \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Grab the GNU ARM toolchain from arm.com
|
||||
# Then extract contents to /opt/build/armbin/
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
|
||||
#################################################
|
||||
# This script aids building mayhem inside docker
|
||||
#
|
||||
# Basic usage:
|
||||
# - Build dev container: ./dockerize.sh build
|
||||
# - Build mayhem: ./dockerize.sh
|
||||
#
|
||||
# The image will be automatically build if it
|
||||
# does not exist, but if the dockerfile changes
|
||||
# it need to be rebuilt manually.
|
||||
#
|
||||
# Advanced parameters:
|
||||
# - Get a shell inside the build image to
|
||||
# inspect problems: ./dockerize.sh shell
|
||||
# - Give additional parameters to the container:
|
||||
# ./dockerize.sh -j10
|
||||
# ./dockerize.sh ninja -j10
|
||||
# - Use a different dockerfile:
|
||||
# ./dockerize.sh build dockerfile-other
|
||||
# - Use a different cpu architecture:
|
||||
# ./dockerize.sh build dockerfile-nogit-arm arm64
|
||||
#
|
||||
# Environment variables:
|
||||
# - It is possible to override the default image
|
||||
# name that is being used to build and run the
|
||||
# build container using an environment variable.
|
||||
# The default is 'portapack-dev'
|
||||
# Override by setting the following environment
|
||||
# variable: MAYHEM_DEV_DOCKER_IMAGE
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 u-foka
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
set -e # exit immediatelly on any failure
|
||||
|
||||
DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
|
||||
IMAGE="${MAYHEM_DEV_DOCKER_IMAGE:-portapack-dev}"
|
||||
|
||||
build_image() {
|
||||
DOCKERFILE=${1:-dockerfile-nogit}
|
||||
PLATFORM=${2:-amd64}
|
||||
docker build --platform "linux/${PLATFORM}" -t "${IMAGE}" -f "${DOCKERFILE}" .
|
||||
}
|
||||
|
||||
start_docker() {
|
||||
if [ -z "$(docker images -q ${IMAGE} 2> /dev/null)" ]; then
|
||||
build_image
|
||||
fi
|
||||
|
||||
exec docker run -v "${DIR}:/havoc" -u "$(id -u):$(id -g)" -ti --rm "${IMAGE}" "$@"
|
||||
}
|
||||
|
||||
if [ "$1" = 'shell' ]; then # open a shell into the container
|
||||
start_docker "bash -li"
|
||||
elif [ "$1" = 'build' ]; then # build the default (or specified) target with ninja
|
||||
shift # remove the first item from $@ as we consumed it, we can then pass the rest on to make
|
||||
build_image "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
start_docker "$@"
|
||||
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(firmware)
|
||||
|
||||
|
||||
@@ -305,7 +305,6 @@ set(CPPSRC
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
apps/ui_sd_wipe.cpp
|
||||
apps/ui_search.cpp
|
||||
apps/ui_settings.cpp
|
||||
apps/ui_siggen.cpp
|
||||
@@ -317,9 +316,7 @@ set(CPPSRC
|
||||
apps/ui_text_editor.cpp
|
||||
apps/ui_touch_calibration.cpp
|
||||
apps/ui_touchtunes.cpp
|
||||
apps/ui_view_wav.cpp
|
||||
apps/ui_weatherstation.cpp
|
||||
apps/ui_whipcalc.cpp
|
||||
protocols/aprs.cpp
|
||||
protocols/ax25.cpp
|
||||
protocols/bht.cpp
|
||||
@@ -377,7 +374,7 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
|
||||
hw
|
||||
apps
|
||||
protocols
|
||||
bitmaps
|
||||
bmps
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
@@ -39,6 +39,14 @@ using namespace portapack;
|
||||
using namespace modems;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#define BLE_RX_NO_ERROR 0
|
||||
#define BLE_RX_LIST_FILENAME_EMPTY_ERROR 1
|
||||
#define BLE_RX_ENTRY_FILENAME_EMPTY_ERROR 2
|
||||
#define BLE_RX_LIST_SAVE_ERROR 3
|
||||
#define BLE_RX_ENTRY_SAVE_ERROR 4
|
||||
|
||||
static uint8_t ble_rx_error = BLE_RX_NO_ERROR;
|
||||
|
||||
void BLELogger::log_raw_data(const std::string& data) {
|
||||
log_file.write_entry(data);
|
||||
}
|
||||
@@ -202,19 +210,23 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
|
||||
ensure_directory(packet_save_path);
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
|
||||
saveFile(new_path, packetToSave);
|
||||
if (value.length() > 0) {
|
||||
ensure_directory(packet_save_path);
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
ble_rx_error = saveFile(new_path, packetToSave);
|
||||
} else {
|
||||
nav_.pop();
|
||||
ble_rx_error = BLE_RX_ENTRY_FILENAME_EMPTY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
|
||||
File f;
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
return BLE_RX_ENTRY_SAVE_ERROR;
|
||||
|
||||
std::string macAddressStr = packetToSave.macAddress;
|
||||
std::string advertisementDataStr = packetToSave.advertisementData;
|
||||
@@ -224,7 +236,7 @@ bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETx
|
||||
|
||||
f.write(packetString.c_str(), packetString.length());
|
||||
|
||||
return true;
|
||||
return BLE_RX_NO_ERROR;
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::update_data() {
|
||||
@@ -568,11 +580,14 @@ std::string BLERxView::build_line_str(BleRecentEntry entry) {
|
||||
}
|
||||
|
||||
void BLERxView::on_save_file(const std::string value) {
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
|
||||
saveFile(new_path);
|
||||
if (value.length() > 0) {
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
ble_rx_error = saveFile(new_path);
|
||||
} else {
|
||||
ble_rx_error = BLE_RX_LIST_FILENAME_EMPTY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
@@ -584,7 +599,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
auto error = src->open(path, false, true);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
return BLE_RX_LIST_SAVE_ERROR;
|
||||
}
|
||||
|
||||
for (const auto& entry : recent) {
|
||||
@@ -615,7 +630,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
auto error = dst->open(tempFilePath, false, true);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
return BLE_RX_LIST_SAVE_ERROR;
|
||||
}
|
||||
|
||||
dst->write_line(headerStr.c_str());
|
||||
@@ -695,7 +710,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
|
||||
tempList.clear();
|
||||
|
||||
return true;
|
||||
return BLE_RX_NO_ERROR;
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
@@ -831,6 +846,18 @@ void BLERxView::on_timer() {
|
||||
baseband::set_btlerx(randomChannel);
|
||||
}
|
||||
}
|
||||
if (ble_rx_error != BLE_RX_NO_ERROR) {
|
||||
if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
|
||||
nav_.display_modal("Error", "List filename is empty !");
|
||||
} else if (ble_rx_error == BLE_RX_ENTRY_FILENAME_EMPTY_ERROR) {
|
||||
nav_.display_modal("Error", "Entry filename is empty !");
|
||||
} else if (ble_rx_error == BLE_RX_LIST_SAVE_ERROR) {
|
||||
nav_.display_modal("Error", "Couldn't save list !");
|
||||
} else if (ble_rx_error == BLE_RX_ENTRY_SAVE_ERROR) {
|
||||
nav_.display_modal("Error", "Couldn't save entry !");
|
||||
}
|
||||
ble_rx_error = BLE_RX_NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void BLERxView::handle_entries_sort(uint8_t index) {
|
||||
|
||||
@@ -55,22 +55,24 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
option_format.set_selected_index(0); // Default to C16
|
||||
option_format.set_selected_index(file_format);
|
||||
option_format.on_change = [this](size_t, uint32_t file_type) {
|
||||
file_format = file_type;
|
||||
record_view.set_file_type((RecordView::FileType)file_type);
|
||||
};
|
||||
|
||||
check_trim.set_value(trim);
|
||||
check_trim.on_select = [this](Checkbox&, bool v) {
|
||||
trim = v;
|
||||
record_view.set_auto_trim(v);
|
||||
};
|
||||
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t new_capture_rate) {
|
||||
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
||||
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
||||
auto sample_rate = bandwidth;
|
||||
|
||||
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* capture_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
||||
|
||||
@@ -78,7 +80,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
|
||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||
// NB: record_view is what actually updates proc_capture baseband settings.
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(new_capture_rate);
|
||||
|
||||
// Update the radio model with the actual sampling rate.
|
||||
receiver_model.set_sampling_rate(actual_sample_rate);
|
||||
@@ -88,19 +90,19 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||
|
||||
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
|
||||
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
|
||||
if ((new_capture_rate >= 1500000) && (capture_rate < 1500000)) {
|
||||
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
|
||||
}
|
||||
if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
|
||||
if ((new_capture_rate <= 1250000) && (capture_rate > 1250000)) {
|
||||
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
|
||||
}
|
||||
previous_bandwidth = bandwidth;
|
||||
capture_rate = new_capture_rate;
|
||||
|
||||
waterfall.start();
|
||||
};
|
||||
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_by_value(500000);
|
||||
option_bandwidth.set_by_value(capture_rate);
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
|
||||
@@ -48,12 +48,21 @@ class CaptureAppView : public View {
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
uint32_t previous_bandwidth{500000};
|
||||
|
||||
uint32_t capture_rate{500000};
|
||||
uint32_t file_format{0};
|
||||
bool trim{false};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_capture", app_settings::Mode::RX};
|
||||
"rx_capture",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"capture_rate"sv, &capture_rate},
|
||||
{"file_format"sv, &file_format},
|
||||
{"trim"sv, &trim},
|
||||
}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
@@ -7,30 +7,33 @@
|
||||
namespace ui {
|
||||
|
||||
// Information: a line starting with a '#' will be yellow coloured
|
||||
constexpr std::string_view authors_list[] = {
|
||||
"# * List of contributors * ",
|
||||
constexpr std::string_view mayhem_information_list[] = {
|
||||
"#****** Mayhem Community ******",
|
||||
" ",
|
||||
" https://discord.mayhem.app",
|
||||
" ",
|
||||
"#**** List of contributors ****",
|
||||
" ",
|
||||
"#Mayhem-Firmware:",
|
||||
"jboone,eried,furrtek,",
|
||||
"NotherNgineer,gullradriel,",
|
||||
"jLynx,kallanreed,Brumi-2021,",
|
||||
"htotoo,bernd-herzog,zxkmm,",
|
||||
"htotoo,zxkmm,bernd-herzog,",
|
||||
"ArjanOnwezen,euquiq,u-foka,",
|
||||
"iNetro,heurist1,dqs105,",
|
||||
"teixeluis,jwetzell,",
|
||||
"jimilinuxguy,gregoryfenton,",
|
||||
"notpike,strijar,BehleZebub,",
|
||||
"arneluehrs,rascafr,joyel24,",
|
||||
"ImDroided,zigad,johnelder,",
|
||||
"klockee,nnesetto,LupusE,",
|
||||
"argilo,dc2dc,formtapez,",
|
||||
"arneluehrs,mcules,rascafr,",
|
||||
"joyel24,ImDroided,zigad,",
|
||||
"johnelder,klockee,nnesetto,",
|
||||
"LupusE,argilo,dc2dc,formtapez,",
|
||||
"RocketGod-git,mrmookie,",
|
||||
"ITAxReal,F33RNI,F4GEV,",
|
||||
"rusty-labs,mjwaxios,andrej-mk,",
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox,",
|
||||
"ckuethe",
|
||||
"MatiasFernandez,Giorgiofox",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
@@ -72,7 +75,7 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (auto& authors_line : authors_list) {
|
||||
for (auto& authors_line : mayhem_information_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
@@ -103,15 +106,13 @@ void AboutView::on_frame_sync() {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
menu_view.set_highlighted(3); // contributors block starting line
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
|
||||
@@ -99,13 +99,21 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
field_frequency.set_value(aprs_rx_freq);
|
||||
} else if (i == 1) { // NA - North America - is also the default
|
||||
field_frequency.set_value(144390000);
|
||||
} else if (i == 2) { // EUR
|
||||
field_frequency.set_value(144800000);
|
||||
} else if (i == 3) { // AUS
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 4) { // NZ
|
||||
} else if (i == 2) { // NZ
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 5) { // ISS
|
||||
} else if (i == 3) { // JAP
|
||||
field_frequency.set_value(144640000);
|
||||
} else if (i == 4) { // PHI
|
||||
field_frequency.set_value(144740000);
|
||||
} else if (i == 5) { // EUR
|
||||
field_frequency.set_value(144800000);
|
||||
} else if (i == 6) { // THA
|
||||
field_frequency.set_value(144900000);
|
||||
} else if (i == 7) { // AUS
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 8) { // BR
|
||||
field_frequency.set_value(145570000);
|
||||
} else if (i == 9) { // ISS
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
options_region_id = i;
|
||||
|
||||
@@ -227,10 +227,14 @@ class APRSRxView : public View {
|
||||
3,
|
||||
{{"MAN", 0},
|
||||
{"NA ", 1},
|
||||
{"EUR", 2},
|
||||
{"AUS", 3},
|
||||
{"NZ ", 4},
|
||||
{"ISS", 5}}};
|
||||
{"NZ ", 2},
|
||||
{"JAP", 3},
|
||||
{"PHI", 4},
|
||||
{"EUR", 5},
|
||||
{"THA", 6},
|
||||
{"AUS", 7},
|
||||
{"BR ", 8},
|
||||
{"ISS", 9}}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16}};
|
||||
|
||||
@@ -92,6 +92,9 @@ void ExternalModuleView::on_tick_second() {
|
||||
case app_location_t::TX:
|
||||
btnText += " (TX)";
|
||||
break;
|
||||
case app_location_t::SETTINGS:
|
||||
btnText += " (Settings)";
|
||||
break;
|
||||
case app_location_t::DEBUG:
|
||||
btnText += " (Debug)";
|
||||
break;
|
||||
|
||||
@@ -177,4 +177,4 @@ void FlashUtilityView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace fs = std::filesystem;
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_max17055.hpp"
|
||||
|
||||
#include "file_reader.hpp"
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
@@ -324,6 +326,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_fake_brightness,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
@@ -361,6 +364,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
||||
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
||||
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
@@ -389,6 +393,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
||||
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
||||
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
@@ -702,14 +707,34 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
&field_encoder_dial_sensitivity,
|
||||
&field_encoder_rate_multiplier,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
&button_cancel,
|
||||
&button_dial_sensitivity_plus,
|
||||
&button_dial_sensitivity_minus,
|
||||
&button_rate_multiplier_plus,
|
||||
&button_rate_multiplier_minus,
|
||||
&field_encoder_dial_direction});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
|
||||
|
||||
button_dial_sensitivity_plus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(1);
|
||||
};
|
||||
button_dial_sensitivity_minus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(-1);
|
||||
};
|
||||
button_rate_multiplier_plus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(1);
|
||||
};
|
||||
button_rate_multiplier_minus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(-1);
|
||||
};
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
|
||||
pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -777,13 +802,20 @@ void SetConfigModeView::focus() {
|
||||
/* SetDisplayView ************************************/
|
||||
|
||||
SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
add_children({&button_save,
|
||||
add_children({&labels,
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_invert_switch});
|
||||
&checkbox_invert_switch,
|
||||
&checkbox_brightness_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());
|
||||
@@ -792,6 +824,14 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
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();
|
||||
};
|
||||
@@ -957,67 +997,6 @@ void SetMenuColorView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetAutoStartView ************************************/
|
||||
|
||||
SetAutostartView::SetAutostartView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&button_reset,
|
||||
&options});
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
autostart_app = "";
|
||||
if (selected != 0) {
|
||||
auto it = full_app_list.find(selected);
|
||||
if (it != full_app_list.end())
|
||||
autostart_app = it->second;
|
||||
}
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_reset.on_select = [this](Button&) {
|
||||
selected = 0;
|
||||
options.set_selected_index(0);
|
||||
autostart_app = "";
|
||||
};
|
||||
|
||||
// options
|
||||
i = 0;
|
||||
OptionsField::option_t o{"-none-", i};
|
||||
opts.emplace_back(o);
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.id == nullptr) continue;
|
||||
i++;
|
||||
o = {app.displayName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.id);
|
||||
if (autostart_app == app.id) selected = i;
|
||||
}
|
||||
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
|
||||
if (app.appCallName == nullptr) return;
|
||||
i++;
|
||||
OptionsField::option_t o = {app.appFriendlyName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.appCallName);
|
||||
if (autostart_app == app.appCallName) selected = i;
|
||||
});
|
||||
|
||||
options.set_options(opts);
|
||||
options.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
selected = v;
|
||||
};
|
||||
options.set_selected_index(selected);
|
||||
}
|
||||
|
||||
void SetAutostartView::focus() {
|
||||
options.focus();
|
||||
}
|
||||
|
||||
/* SetThemeView ************************************/
|
||||
|
||||
SetThemeView::SetThemeView(NavigationView& nav) {
|
||||
@@ -1099,9 +1078,12 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav)
|
||||
}
|
||||
|
||||
void SettingsMenuView::on_populate() {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
const bool return_icon = pmem::show_gui_return_icon();
|
||||
|
||||
if (return_icon) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
|
||||
@@ -1120,9 +1102,11 @@ void SettingsMenuView::on_populate() {
|
||||
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
|
||||
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
|
||||
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetThemeView>(); }},
|
||||
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
|
||||
});
|
||||
|
||||
if (battery::BatteryManagement::isDetected()) add_item({"Battery", ui::Color::dark_cyan(), &bitmap_icon_batt_icon, [this]() { nav_.push<SetBatteryView>(); }});
|
||||
|
||||
add_external_items(nav_, app_location_t::SETTINGS, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -363,8 +363,12 @@ class SetUIView : public View {
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_text};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{23 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
@@ -570,6 +574,7 @@ class SetQRCodeView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::encoder_dial_direction;
|
||||
using portapack::persistent_memory::encoder_dial_sensitivity;
|
||||
using portapack::persistent_memory::encoder_rate_multiplier;
|
||||
|
||||
@@ -583,30 +588,52 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "rotation position (number of", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "steps per full rotation):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Dial sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 9 * 16}, "rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 10 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 12 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 0 * 16}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 5 * 16},
|
||||
{20 * 8, 3 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
NumberField field_encoder_rate_multiplier{
|
||||
{20 * 8, 12 * 16},
|
||||
{20 * 8, 10 * 16},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField field_encoder_dial_direction{
|
||||
{18 * 8, 14 * 16},
|
||||
7,
|
||||
{{"NORMAL", false},
|
||||
{"REVERSE", true}}};
|
||||
|
||||
Button button_dial_sensitivity_plus{
|
||||
{20 * 8, 2 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_dial_sensitivity_minus{
|
||||
{20 * 8, 4 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_rate_multiplier_plus{
|
||||
{20 * 8, 9 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_rate_multiplier_minus{
|
||||
{20 * 8, 11 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -705,6 +732,7 @@ class SetConfigModeView : public View {
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
using portapack::persistent_memory::fake_brightness_level_options;
|
||||
|
||||
class SetDisplayView : public View {
|
||||
public:
|
||||
@@ -715,8 +743,27 @@ 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, 2 * 16},
|
||||
{1 * 8, 10 * 16},
|
||||
23,
|
||||
"Invert colors (For IPS)"};
|
||||
|
||||
@@ -864,47 +911,6 @@ class SetMenuColorView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
class SetAutostartView : public View {
|
||||
public:
|
||||
SetAutostartView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Autostart"; };
|
||||
|
||||
private:
|
||||
int32_t i = 0;
|
||||
std::string autostart_app{""};
|
||||
OptionsField::options_t opts{};
|
||||
std::map<int32_t, std::string> full_app_list{}; // looking table
|
||||
int32_t selected = 0;
|
||||
SettingsStore nav_setting{
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Select app to start on boot", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 2 * 16}, "(an SD Card is required)", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
OptionsField options{
|
||||
{0 * 8, 4 * 16},
|
||||
screen_width / 8,
|
||||
{},
|
||||
true};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 6 * 16, screen_width - 4 * 8, 32},
|
||||
"Reset"};
|
||||
};
|
||||
|
||||
class SetThemeView : public View {
|
||||
public:
|
||||
SetThemeView(NavigationView& nav);
|
||||
@@ -988,6 +994,7 @@ class SettingsMenuView : public BtnGridView {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ SigGenView::~SigGenView() {
|
||||
|
||||
void SigGenView::update_config() {
|
||||
if (checkbox_stop.value())
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), field_stop.value());
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), field_stop.value());
|
||||
else
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), 0);
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), 0);
|
||||
}
|
||||
|
||||
void SigGenView::update_tone() {
|
||||
@@ -78,6 +78,7 @@ SigGenView::SigGenView(
|
||||
baseband::run_image(portapack::spi_flash::image_tag_siggen);
|
||||
|
||||
add_children({&labels,
|
||||
&options_mod,
|
||||
&options_shape,
|
||||
&text_shape,
|
||||
&symfield_tone,
|
||||
@@ -87,22 +88,48 @@ SigGenView::SigGenView(
|
||||
&field_stop,
|
||||
&tx_view});
|
||||
|
||||
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
|
||||
symfield_tone.hidden(true); // At first launch , by default we are in CW: Shape ignored, we are not using Tone modulation.
|
||||
options_shape.hidden(true);
|
||||
text_shape.hidden(true);
|
||||
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
text_shape.set(shape_strings[v]);
|
||||
if (auto_update)
|
||||
update_config();
|
||||
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(1);
|
||||
|
||||
if (v == 5) { // In Shape Pseudo Random Noise we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(true);
|
||||
} else {
|
||||
symfield_tone.hidden(0);
|
||||
symfield_tone.hidden(false);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
};
|
||||
options_shape.set_selected_index(0);
|
||||
text_shape.set(shape_strings[0]);
|
||||
|
||||
options_mod.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (auto_update)
|
||||
update_config();
|
||||
|
||||
if (v == 0) { // In Modulation Options CW we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(true);
|
||||
} else {
|
||||
symfield_tone.hidden(false);
|
||||
}
|
||||
|
||||
if ((v == 0) || (v == 2) || (v == 3) || (v == 7)) { // In Modulation Options CW, QPSK, BPSK, Pulsed CW we are not using Shapes.
|
||||
options_shape.hidden(true);
|
||||
text_shape.hidden(true);
|
||||
} else {
|
||||
options_shape.hidden(false);
|
||||
text_shape.hidden(false);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
};
|
||||
options_mod.set_selected_index(0);
|
||||
|
||||
field_stop.set_value(1);
|
||||
|
||||
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||
|
||||
@@ -58,68 +58,74 @@ class SigGenView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_siggen", app_settings::Mode::TX};
|
||||
|
||||
const std::string shape_strings[9] = {
|
||||
"CW (No mod.) ",
|
||||
"Sine mod. FM",
|
||||
"Triangle mod.FM", // max 15 character text space.
|
||||
"Saw up mod. FM",
|
||||
"Saw down mod.FM",
|
||||
"Square mod. FM",
|
||||
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
|
||||
"BPSK 0,1,0,1...",
|
||||
"QPSK 00-01-10.."};
|
||||
const std::string shape_strings[6] = {// max 15 character text space.
|
||||
"Sine",
|
||||
"Triangle",
|
||||
"Saw up",
|
||||
"Saw down",
|
||||
"Square",
|
||||
"Pseudo Noise"};
|
||||
|
||||
bool auto_update{false};
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 4 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{22 * 8, 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground},
|
||||
{{8 * 8, 20 * 8}, "Modulation: FM", Theme::getInstance()->fg_light->foreground}};
|
||||
{{3 * 8, 2 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 3 * 8 + 8 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 2 * 8 + 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{22 * 8, 2 * 8 + 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
ImageOptionsField options_shape{
|
||||
{10 * 8, 4, 32, 32},
|
||||
{10 * 8, 3 * 8 + 8, 32, 32},
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
Theme::getInstance()->bg_darkest->background,
|
||||
{{&bitmap_sig_cw, 0},
|
||||
{&bitmap_sig_sine, 1},
|
||||
{&bitmap_sig_tri, 2},
|
||||
{&bitmap_sig_saw_up, 3},
|
||||
{&bitmap_sig_saw_down, 4},
|
||||
{&bitmap_sig_square, 5},
|
||||
{&bitmap_sig_noise, 6},
|
||||
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
|
||||
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
|
||||
{{&bitmap_sig_sine, 0},
|
||||
{&bitmap_sig_tri, 1},
|
||||
{&bitmap_sig_saw_up, 2},
|
||||
{&bitmap_sig_saw_down, 3},
|
||||
{&bitmap_sig_square, 4},
|
||||
{&bitmap_sig_noise, 5}}};
|
||||
|
||||
Text text_shape{
|
||||
{15 * 8, 4 + 10, 15 * 8, 16},
|
||||
{15 * 8, 3 * 8 + 8 + 10, 15 * 8, 16},
|
||||
""};
|
||||
|
||||
SymField symfield_tone{
|
||||
{12 * 8, 7 * 8},
|
||||
{12 * 8, 2 * 8 + 7 * 8},
|
||||
5};
|
||||
|
||||
Button button_update{
|
||||
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
|
||||
{5 * 8, 2 * 8 + 10 * 8, 8 * 8, 3 * 8},
|
||||
"Update"};
|
||||
|
||||
Checkbox checkbox_auto{
|
||||
{15 * 8, 10 * 8},
|
||||
{15 * 8, 2 * 8 + 10 * 8},
|
||||
4,
|
||||
"Auto"};
|
||||
|
||||
Checkbox checkbox_stop{
|
||||
{5 * 8, 15 * 8},
|
||||
{5 * 8, 2 * 8 + 15 * 8},
|
||||
10,
|
||||
"Stop after"};
|
||||
|
||||
NumberField field_stop{
|
||||
{20 * 8, 15 * 8 + 4},
|
||||
{20 * 8, 2 * 8 + 15 * 8 + 4},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField options_mod{
|
||||
{15 * 8, 2 * 8},
|
||||
12,
|
||||
{{"CW (No mod.)", 0},
|
||||
{"FM", 1},
|
||||
{"BPSK", 2},
|
||||
{"QPSK", 3},
|
||||
{"DSB", 4},
|
||||
{"AM 100% dep.", 5},
|
||||
{"AM 50% depth", 6},
|
||||
{"Pulse CW 25%", 7}}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
10000,
|
||||
|
||||
+153
-76
@@ -987,44 +987,6 @@ static constexpr Bitmap bitmap_icon_previous{
|
||||
{16, 16},
|
||||
bitmap_icon_previous_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_tools_antenna_data[] = {
|
||||
0x38,
|
||||
0x3E,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x26,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x2E,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x26,
|
||||
0x10,
|
||||
0x22,
|
||||
0x38,
|
||||
0x2E,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x26,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x2E,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x3E,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_tools_antenna{
|
||||
{16, 16},
|
||||
bitmap_icon_tools_antenna_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_batt_text_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -1799,6 +1761,45 @@ static constexpr uint8_t bitmap_icon_lge_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
|
||||
0xFC,
|
||||
0x03,
|
||||
0x04,
|
||||
0x06,
|
||||
0x04,
|
||||
0x0E,
|
||||
0x04,
|
||||
0x1E,
|
||||
0x04,
|
||||
0x3E,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x21,
|
||||
0x44,
|
||||
0x25,
|
||||
0x54,
|
||||
0x25,
|
||||
0xF4,
|
||||
0x2F,
|
||||
0xA4,
|
||||
0x2A,
|
||||
0x84,
|
||||
0x22,
|
||||
0x04,
|
||||
0x22,
|
||||
0x04,
|
||||
0x20,
|
||||
0xFC,
|
||||
0x3F,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_file_iq{
|
||||
{16, 16},
|
||||
bitmap_icon_file_iq_data};
|
||||
|
||||
static constexpr Bitmap bitmap_icon_lge{
|
||||
{16, 16},
|
||||
bitmap_icon_lge_data};
|
||||
@@ -2531,6 +2532,44 @@ static constexpr Bitmap bitmap_icon_batt_icon{
|
||||
{16, 16},
|
||||
bitmap_icon_batt_icon_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_temperature_data[] = {
|
||||
0x00,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x05,
|
||||
0xC0,
|
||||
0x0D,
|
||||
0x40,
|
||||
0x0D,
|
||||
0xD0,
|
||||
0x1F,
|
||||
0x70,
|
||||
0x15,
|
||||
0xB0,
|
||||
0x1A,
|
||||
0x58,
|
||||
0x35,
|
||||
0xB8,
|
||||
0x3A,
|
||||
0x58,
|
||||
0x34,
|
||||
0x28,
|
||||
0x28,
|
||||
0x18,
|
||||
0x30,
|
||||
0x30,
|
||||
0x18,
|
||||
0x60,
|
||||
0x0C,
|
||||
0xC0,
|
||||
0x07,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_temperature{
|
||||
{16, 16},
|
||||
bitmap_icon_temperature_data};
|
||||
|
||||
static constexpr uint8_t bitmap_tab_edge_data[] = {
|
||||
0x00,
|
||||
0x01,
|
||||
@@ -2675,44 +2714,6 @@ static constexpr Bitmap bitmap_icon_touchtunes{
|
||||
{16, 16},
|
||||
bitmap_icon_touchtunes_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
|
||||
0xFC,
|
||||
0x03,
|
||||
0x04,
|
||||
0x06,
|
||||
0x04,
|
||||
0x0E,
|
||||
0x04,
|
||||
0x1E,
|
||||
0x04,
|
||||
0x3E,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x21,
|
||||
0x44,
|
||||
0x25,
|
||||
0x54,
|
||||
0x25,
|
||||
0xF4,
|
||||
0x2F,
|
||||
0xA4,
|
||||
0x2A,
|
||||
0x84,
|
||||
0x22,
|
||||
0x04,
|
||||
0x22,
|
||||
0x04,
|
||||
0x20,
|
||||
0xFC,
|
||||
0x3F,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_file_iq{
|
||||
{16, 16},
|
||||
bitmap_icon_file_iq_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_sd_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -4789,6 +4790,44 @@ static constexpr Bitmap bitmap_icon_ais{
|
||||
{16, 16},
|
||||
bitmap_icon_ais_data};
|
||||
|
||||
static constexpr uint8_t bitmap_temperature_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,
|
||||
};
|
||||
static constexpr Bitmap bitmap_temperature{
|
||||
{16, 16},
|
||||
bitmap_temperature_data};
|
||||
|
||||
static constexpr uint8_t bitmap_titlebar_image_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -5701,6 +5740,44 @@ static constexpr Bitmap bitmap_icon_options_datetime{
|
||||
{16, 16},
|
||||
bitmap_icon_options_datetime_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_tune_fork_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x22,
|
||||
0x44,
|
||||
0x21,
|
||||
0x84,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x25,
|
||||
0xA4,
|
||||
0x25,
|
||||
0xA4,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x61,
|
||||
0x86,
|
||||
0xC2,
|
||||
0x43,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_tune_fork{
|
||||
{16, 16},
|
||||
bitmap_icon_tune_fork_data};
|
||||
|
||||
static constexpr uint8_t bitmap_target_verify_data[] = {
|
||||
0x00,
|
||||
0xE0,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::antenna_length {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<WhipCalcView>();
|
||||
}
|
||||
} // namespace ui::external_app::antenna_length
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Antenna Length",
|
||||
/*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 0x00},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+3
-2
@@ -32,8 +32,9 @@
|
||||
#include <cstring>
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::antenna_length {
|
||||
|
||||
void WhipCalcView::focus() {
|
||||
field_frequency.focus();
|
||||
@@ -176,4 +177,4 @@ void WhipCalcView::load_antenna_db() {
|
||||
void WhipCalcView::add_default_antenna() {
|
||||
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
|
||||
}
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::antenna_length
|
||||
+5
-2
@@ -31,7 +31,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::antenna_length {
|
||||
|
||||
class WhipCalcView : public View {
|
||||
public:
|
||||
WhipCalcView(NavigationView& nav);
|
||||
@@ -90,6 +93,6 @@ class WhipCalcView : public View {
|
||||
"Back"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::antenna_length
|
||||
|
||||
#endif /*__UI_WHIPCALC__*/
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_app_manager.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::app_manager {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<AppManagerView>();
|
||||
}
|
||||
} // namespace ui::external_app::app_manager
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_app_manager.application_information"), used)) application_information_t _application_information_app_manager = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::app_manager::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "AppManager",
|
||||
/*.bitmap_data = */ {
|
||||
0xE0,
|
||||
0x00,
|
||||
0x10,
|
||||
0x01,
|
||||
0x10,
|
||||
0x01,
|
||||
0x1F,
|
||||
0x1F,
|
||||
0x01,
|
||||
0x10,
|
||||
0x01,
|
||||
0x10,
|
||||
0x01,
|
||||
0x10,
|
||||
0x01,
|
||||
0x70,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x70,
|
||||
0xE1,
|
||||
0x10,
|
||||
0x11,
|
||||
0x11,
|
||||
0x11,
|
||||
0x11,
|
||||
0x1F,
|
||||
0x1F,
|
||||
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::SETTINGS,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* copyleft spammingdramaqueen
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_app_manager.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "file_reader.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::app_manager {
|
||||
|
||||
/* AppManagerView **************************************/
|
||||
/* | inner apps | external apps |
|
||||
* ------------------------------
|
||||
* | id | appCallName |
|
||||
* | displayName|appFriendlyName|
|
||||
*/
|
||||
AppManagerView::AppManagerView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
&menu_view,
|
||||
&text_app_info,
|
||||
&button_hide_unhide,
|
||||
&button_clean_hide,
|
||||
&button_set_cancel_autostart,
|
||||
&button_clean_autostart});
|
||||
|
||||
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
if (menu_view.highlighted_index() >= app_list_index) {
|
||||
text_app_info.set("");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string info;
|
||||
auto app_name = get_app_info(menu_view.highlighted_index(), true);
|
||||
auto app_id = get_app_info(menu_view.highlighted_index(), false);
|
||||
|
||||
info += "Hidden:";
|
||||
|
||||
if (is_blacklisted(app_name)) {
|
||||
info += "Yes ";
|
||||
} else {
|
||||
info += "No ";
|
||||
}
|
||||
|
||||
info += "Autostart:";
|
||||
if (is_autostart_app(app_id)) {
|
||||
info += "Yes";
|
||||
} else {
|
||||
info += "No";
|
||||
}
|
||||
|
||||
if (info.empty()) {
|
||||
info = "Highlight an app";
|
||||
}
|
||||
|
||||
text_app_info.set(info);
|
||||
};
|
||||
|
||||
button_hide_unhide.on_select = [this](Button&) {
|
||||
hide_unhide_app();
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
button_clean_hide.on_select = [this](Button&) {
|
||||
clean_blacklist();
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
button_set_cancel_autostart.on_select = [this](Button&) {
|
||||
set_unset_autostart_app();
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
button_clean_autostart.on_select = [this](Button&) {
|
||||
unset_auto_start();
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void AppManagerView::refresh_list() {
|
||||
auto previous_cursor_index = menu_view.highlighted_index();
|
||||
app_list_index = 0;
|
||||
menu_view.clear();
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
auto padding = [](const std::string& str) {
|
||||
return str.length() < 25 ? std::string(25 - str.length(), ' ') + '' : "";
|
||||
// that weird char is a icon in portapack's font base that looks like a switch, which i use to indicate the auto start
|
||||
};
|
||||
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.id == nullptr) continue;
|
||||
|
||||
app_list_index++;
|
||||
|
||||
menu_view.add_item({app.displayName + std::string(is_autostart_app(app.id) ? padding(app.displayName) : ""),
|
||||
app.iconColor,
|
||||
app.icon,
|
||||
[this, app_id = std::string(app.id)](KeyEvent) {
|
||||
button_hide_unhide.focus();
|
||||
}});
|
||||
}
|
||||
|
||||
ExternalItemsMenuLoader::load_all_external_items_callback([this, &index, &padding](ui::AppInfoConsole& app) {
|
||||
if (app.appCallName == nullptr) return;
|
||||
|
||||
app_list_index++;
|
||||
|
||||
menu_view.add_item({app.appFriendlyName + std::string(is_autostart_app(app.appCallName) ? padding(app.appFriendlyName) : ""),
|
||||
ui::Color::light_grey(),
|
||||
&bitmap_icon_sdcard,
|
||||
[this, app_id = std::string(app.appCallName)](KeyEvent) {
|
||||
button_hide_unhide.focus();
|
||||
}});
|
||||
});
|
||||
|
||||
menu_view.set_highlighted(previous_cursor_index);
|
||||
}
|
||||
|
||||
void AppManagerView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
void AppManagerView::hide_app() {
|
||||
std::vector<std::string> blacklist;
|
||||
get_blacklist(blacklist);
|
||||
|
||||
blacklist.push_back(get_app_info(menu_view.highlighted_index(), true));
|
||||
write_blacklist(blacklist);
|
||||
}
|
||||
|
||||
void AppManagerView::unhide_app() {
|
||||
std::vector<std::string> blacklist;
|
||||
get_blacklist(blacklist);
|
||||
blacklist.erase(std::remove(blacklist.begin(), blacklist.end(), get_app_info(menu_view.highlighted_index(), true)), blacklist.end());
|
||||
write_blacklist(blacklist);
|
||||
}
|
||||
|
||||
void AppManagerView::hide_unhide_app() {
|
||||
if (is_blacklisted(get_app_info(menu_view.highlighted_index(), true)))
|
||||
unhide_app();
|
||||
else
|
||||
hide_app();
|
||||
}
|
||||
|
||||
void AppManagerView::get_blacklist(std::vector<std::string>& blacklist) {
|
||||
File f;
|
||||
|
||||
auto error = f.open(u"SETTINGS/blacklist");
|
||||
if (error)
|
||||
return;
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
blacklist.push_back(trim(line));
|
||||
}
|
||||
}
|
||||
|
||||
void AppManagerView::write_blacklist(const std::vector<std::string>& blacklist) {
|
||||
File f;
|
||||
auto error = f.create(u"SETTINGS/blacklist");
|
||||
if (error)
|
||||
return;
|
||||
|
||||
for (const auto& app_name : blacklist) {
|
||||
auto line = app_name + "\n";
|
||||
f.write(line.c_str(), line.length());
|
||||
}
|
||||
}
|
||||
|
||||
void AppManagerView::clean_blacklist() {
|
||||
std::vector<std::string> blacklist = {};
|
||||
write_blacklist(blacklist);
|
||||
}
|
||||
|
||||
bool AppManagerView::is_blacklisted(const std::string& app_name) {
|
||||
std::vector<std::string> blacklist;
|
||||
get_blacklist(blacklist);
|
||||
return std::find(blacklist.begin(), blacklist.end(), app_name) != blacklist.end();
|
||||
}
|
||||
|
||||
void AppManagerView::set_auto_start() {
|
||||
auto app_index = menu_view.highlighted_index();
|
||||
if (app_index >= app_list_index) return;
|
||||
|
||||
auto id_aka_app_call_name = get_app_info(app_index, false);
|
||||
|
||||
autostart_app = id_aka_app_call_name;
|
||||
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void AppManagerView::unset_auto_start() {
|
||||
autostart_app = "";
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void AppManagerView::set_unset_autostart_app() {
|
||||
auto app_index = menu_view.highlighted_index();
|
||||
if (app_index >= app_list_index) return;
|
||||
|
||||
auto id_aka_friendly_name = get_app_info(app_index, false);
|
||||
|
||||
if (is_autostart_app(id_aka_friendly_name))
|
||||
unset_auto_start();
|
||||
else
|
||||
set_auto_start();
|
||||
}
|
||||
|
||||
bool AppManagerView::is_autostart_app(const char* id_aka_friendly_name) {
|
||||
return autostart_app == std::string(id_aka_friendly_name);
|
||||
}
|
||||
|
||||
bool AppManagerView::is_autostart_app(const std::string& id_aka_friendly_name) {
|
||||
return autostart_app == id_aka_friendly_name;
|
||||
}
|
||||
|
||||
std::string AppManagerView::get_app_info(uint16_t index, bool is_display_name) {
|
||||
size_t current_index = 0;
|
||||
std::string result;
|
||||
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.id == nullptr) continue;
|
||||
if (current_index == index) {
|
||||
return is_display_name ? std::string(app.displayName) : std::string(app.id);
|
||||
}
|
||||
current_index++;
|
||||
}
|
||||
|
||||
ExternalItemsMenuLoader::load_all_external_items_callback([¤t_index, &index, &result, &is_display_name](ui::AppInfoConsole& app) {
|
||||
if (app.appCallName == nullptr) return;
|
||||
if (current_index == index) {
|
||||
result = is_display_name ? app.appFriendlyName : app.appCallName;
|
||||
}
|
||||
current_index++;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::app_manager
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* copyleft spammingdramaqueen
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_APP_MANAGER_H__
|
||||
#define __UI_APP_MANAGER_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
namespace ui::external_app::app_manager {
|
||||
|
||||
class AppManagerView : public View {
|
||||
public:
|
||||
AppManagerView(NavigationView& nav);
|
||||
std::string title() const override { return "AppMan"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string autostart_app{""};
|
||||
SettingsStore nav_setting{
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
|
||||
void focus() override;
|
||||
void refresh_list();
|
||||
uint16_t app_list_index{0};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "App list:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
MenuView menu_view{};
|
||||
|
||||
Text text_app_info{
|
||||
{0, 27 * 8, screen_width, 16},
|
||||
"Highlight an app"};
|
||||
|
||||
Button button_hide_unhide{
|
||||
{0, 29 * 8, screen_width / 2 - 1, 32},
|
||||
"Hide/Show"};
|
||||
|
||||
Button button_clean_hide{
|
||||
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
|
||||
"Clean Hidden"};
|
||||
|
||||
Button button_set_cancel_autostart{
|
||||
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
|
||||
"Set Autostart"};
|
||||
|
||||
Button button_clean_autostart{
|
||||
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
|
||||
"Del Autostart"};
|
||||
|
||||
std::string get_app_info(uint16_t index, bool is_display_name);
|
||||
void get_blacklist(std::vector<std::string>& blacklist);
|
||||
void write_blacklist(const std::vector<std::string>& blacklist);
|
||||
bool is_blacklisted(const std::string& app_display_name);
|
||||
void hide_app();
|
||||
void unhide_app();
|
||||
void hide_unhide_app();
|
||||
void clean_blacklist();
|
||||
bool is_autostart_app(const std::string& display_name);
|
||||
void set_auto_start();
|
||||
void unset_auto_start();
|
||||
void set_unset_autostart_app();
|
||||
bool is_autostart_app(const char* id_aka_app_call_name);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::app_manager
|
||||
|
||||
#endif // __UI_APP_MANAGER_H__
|
||||
@@ -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 */
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
+46
-1
@@ -134,6 +134,42 @@ set(EXTCPPSRC
|
||||
#mcu_temperature
|
||||
external/mcu_temperature/main.cpp
|
||||
external/mcu_temperature/mcu_temperature.cpp
|
||||
|
||||
#fmradio
|
||||
external/fmradio/main.cpp
|
||||
external/fmradio/ui_fmradio.cpp
|
||||
|
||||
#tuner
|
||||
external/tuner/main.cpp
|
||||
external/tuner/ui_tuner.cpp
|
||||
|
||||
#metronome
|
||||
external/metronome/main.cpp
|
||||
external/metronome/ui_metronome.cpp
|
||||
|
||||
#app_manager
|
||||
external/app_manager/main.cpp
|
||||
external/app_manager/ui_app_manager.cpp
|
||||
|
||||
#hopper
|
||||
external/hopper/main.cpp
|
||||
external/hopper/ui_hopper.cpp
|
||||
|
||||
# whip calculator
|
||||
external/antenna_length/main.cpp
|
||||
external/antenna_length/ui_whipcalc.cpp
|
||||
|
||||
# wav viewer
|
||||
external/wav_view/main.cpp
|
||||
external/wav_view/ui_view_wav.cpp
|
||||
|
||||
# wipe sdcard
|
||||
external/sd_wipe/main.cpp
|
||||
external/sd_wipe/ui_sd_wipe.cpp
|
||||
|
||||
# playlist editor
|
||||
external/playlist_editor/main.cpp
|
||||
external/playlist_editor/ui_playlist_editor.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -167,6 +203,15 @@ set(EXTAPPLIST
|
||||
ook_editor
|
||||
shoppingcart_lock
|
||||
flippertx
|
||||
remote
|
||||
remote
|
||||
mcu_temperature
|
||||
fmradio
|
||||
tuner
|
||||
metronome
|
||||
app_manager
|
||||
hopper
|
||||
antenna_length
|
||||
view_wav
|
||||
sd_wipe
|
||||
playlist_editor
|
||||
)
|
||||
|
||||
+63
@@ -55,6 +55,15 @@ MEMORY
|
||||
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
|
||||
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
|
||||
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
|
||||
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
|
||||
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
|
||||
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
|
||||
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
|
||||
ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k
|
||||
ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k
|
||||
ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k
|
||||
ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k
|
||||
ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -251,4 +260,58 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
||||
*(*ui*external_app*mcu_temperature*);
|
||||
} > ram_external_app_mcu_temperature
|
||||
|
||||
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_fmradio.application_information));
|
||||
*(*ui*external_app*fmradio*);
|
||||
} > ram_external_app_fmradio
|
||||
|
||||
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tuner.application_information));
|
||||
*(*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
|
||||
|
||||
.external_app_app_manager : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_app_manager.application_information));
|
||||
*(*ui*external_app*app_manager*);
|
||||
} > ram_external_app_app_manager
|
||||
|
||||
.external_app_hopper : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_hopper.application_information));
|
||||
*(*ui*external_app*hopper*);
|
||||
} > ram_external_app_hopper
|
||||
|
||||
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_antenna_length.application_information));
|
||||
*(*ui*external_app*antenna_length*);
|
||||
} > ram_external_app_antenna_length
|
||||
|
||||
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_view_wav.application_information));
|
||||
*(*ui*external_app*view_wav*);
|
||||
} > ram_external_app_view_wav
|
||||
|
||||
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_sd_wipe.application_information));
|
||||
*(*ui*external_app*sd_wipe*);
|
||||
} > ram_external_app_sd_wipe
|
||||
|
||||
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_playlist_editor.application_information));
|
||||
*(*ui*external_app*playlist_editor*);
|
||||
} > ram_external_app_playlist_editor
|
||||
}
|
||||
|
||||
@@ -89,8 +89,6 @@ class FlipperTxView : public View {
|
||||
std::unique_ptr<FlipperPlayThread> replay_thread{};
|
||||
Optional<flippersub_metadata> submeta{};
|
||||
|
||||
const std::filesystem::path subghz_dir = u"subghz";
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
|
||||
+83
@@ -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<FmRadioView>();
|
||||
}
|
||||
} // 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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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,
|
||||
true};
|
||||
|
||||
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
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<const AudioSpectrumMessage*>(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__*/
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
* copyleft Whiterose of the Dark Army
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -32,7 +33,7 @@ namespace ui::external_app::font_viewer {
|
||||
|
||||
/* DebugFontsView *******************************************************/
|
||||
|
||||
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
|
||||
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font) {
|
||||
auto char_width{font_style->font.char_width()};
|
||||
auto char_height{font_style->font.line_height()};
|
||||
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
|
||||
@@ -47,7 +48,15 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
|
||||
if ((c % cpl) == 0)
|
||||
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
|
||||
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
|
||||
Style highlight_style_big = *Theme::getInstance()->fg_red;
|
||||
Style highlight_style_small = *Theme::getInstance()->bg_important_small;
|
||||
if (c == field_cursor.value()) {
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos},
|
||||
is_big_font ? highlight_style_big : highlight_style_small,
|
||||
(char)(c + 0x20));
|
||||
} else {
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
|
||||
}
|
||||
}
|
||||
|
||||
return line_pos + char_height;
|
||||
@@ -56,13 +65,29 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
|
||||
void DebugFontsView::paint(Painter& painter) {
|
||||
int16_t line_pos;
|
||||
|
||||
line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16");
|
||||
display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8");
|
||||
line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16", true);
|
||||
display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8", false);
|
||||
}
|
||||
|
||||
void DebugFontsView::update_address_text() {
|
||||
uint8_t ascii_value = field_cursor.value() + 0x20;
|
||||
text_address.set("0x" + to_string_hex(ascii_value, 2));
|
||||
}
|
||||
|
||||
DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&field_cursor,
|
||||
&text_address});
|
||||
set_focusable(true);
|
||||
|
||||
field_cursor.on_change = [&](int32_t) {
|
||||
update_address_text();
|
||||
set_dirty();
|
||||
};
|
||||
}
|
||||
|
||||
void DebugFontsView::focus() {
|
||||
field_cursor.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui::external_app::font_viewer */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
* copyleft Whiterose of the Dark Army
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -39,10 +40,23 @@ class DebugFontsView : public View {
|
||||
public:
|
||||
DebugFontsView(NavigationView& nav);
|
||||
void paint(Painter& painter) override;
|
||||
void focus() override;
|
||||
std::string title() const override { return "Fonts"; };
|
||||
|
||||
private:
|
||||
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
|
||||
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font);
|
||||
void update_address_text();
|
||||
|
||||
NumberField field_cursor{
|
||||
{0 * 8, 0 * 8},
|
||||
1,
|
||||
{0, 1000},
|
||||
1,
|
||||
' '};
|
||||
Text text_address{
|
||||
{screen_width / 2, 0 * 16, screen_width / 2, 16},
|
||||
"0x20",
|
||||
};
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_hopper.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<HopperView>();
|
||||
}
|
||||
} // namespace ui::external_app::hopper
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_hopper.application_information"), used)) application_information_t _application_information_hopper = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::hopper::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Hopper",
|
||||
/*.bitmap_data = */ {
|
||||
0xE0,
|
||||
0x07,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0x1C,
|
||||
0x38,
|
||||
0x0E,
|
||||
0x78,
|
||||
0x06,
|
||||
0x7C,
|
||||
0x03,
|
||||
0xCE,
|
||||
0x03,
|
||||
0xC7,
|
||||
0x83,
|
||||
0xC3,
|
||||
0xC3,
|
||||
0xC1,
|
||||
0xE3,
|
||||
0xC0,
|
||||
0x73,
|
||||
0xC0,
|
||||
0x3E,
|
||||
0x60,
|
||||
0x1E,
|
||||
0x70,
|
||||
0x1C,
|
||||
0x38,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0xE0,
|
||||
0x07,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2020 euquiq
|
||||
* copyleft mr.r0b0t from the F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_hopper.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
|
||||
void HopperView::focus() {
|
||||
button_load_list.focus();
|
||||
}
|
||||
|
||||
HopperView::~HopperView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void HopperView::update_freq_list_menu_view() {
|
||||
menu_freq_list.clear();
|
||||
uint8_t list_count = freq_list.size();
|
||||
|
||||
if (list_count == 0) {
|
||||
menu_freq_list.add_item({"Add freq or load list",
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < list_count; i++) {
|
||||
menu_freq_list.add_item({to_string_rounded_freq(freq_list[i], INT8_MAX),
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void HopperView::on_retune(const rf::Frequency freq, const uint32_t range) {
|
||||
if (freq) {
|
||||
transmitter_model.set_target_frequency(freq);
|
||||
text_range_number.set(to_string_dec_uint(range, 2));
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) {
|
||||
hopper_channels[i].enabled = true;
|
||||
hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
|
||||
hopper_channels[i].center = center;
|
||||
hopper_channels[i].duration = 30720 * duration;
|
||||
}
|
||||
|
||||
void HopperView::start_tx() {
|
||||
uint32_t c, i = 0;
|
||||
bool out_of_ranges = false;
|
||||
|
||||
size_t hop_value = options_hop.selected_index_value();
|
||||
|
||||
// Disable all channels by default
|
||||
for (c = 0; c < JAMMER_MAX_CH; c++)
|
||||
hopper_channels[c].enabled = false;
|
||||
|
||||
uint8_t channel_count = freq_list.size();
|
||||
|
||||
if (channel_count <= JAMMER_MAX_CH) {
|
||||
for (c = 0; c < channel_count; c++) {
|
||||
if (i >= JAMMER_MAX_CH) {
|
||||
out_of_ranges = true;
|
||||
break;
|
||||
}
|
||||
set_hopper_channel(i, JAMMER_CH_WIDTH, freq_list[c], hop_value);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
out_of_ranges = true;
|
||||
}
|
||||
|
||||
if (!out_of_ranges && i) {
|
||||
text_range_total.set("/" + to_string_dec_uint(i, 2));
|
||||
|
||||
jamming = true;
|
||||
button_transmit.set_style(&style_cancel);
|
||||
button_transmit.set_text("STOP");
|
||||
|
||||
transmitter_model.set_rf_amp(field_amp.value());
|
||||
transmitter_model.set_tx_gain(field_gain.value());
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
|
||||
} else {
|
||||
if (out_of_ranges)
|
||||
nav_.display_modal("Error", "Jam freq too much.");
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::stop_tx() {
|
||||
button_transmit.set_style(&style_val);
|
||||
button_transmit.set_text("START");
|
||||
transmitter_model.disable();
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
jamming = false;
|
||||
cooling = false;
|
||||
}
|
||||
|
||||
// called each 1/60th of second
|
||||
void HopperView::on_timer() {
|
||||
if (++mscounter == 60) {
|
||||
mscounter = 0;
|
||||
if (jamming) {
|
||||
if (cooling) {
|
||||
if (++seconds >= field_timepause.value()) { // Re-start TX
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
transmitter_model.enable();
|
||||
button_transmit.set_text("STOP");
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
|
||||
int32_t jitter_amount = field_jitter.value();
|
||||
if (jitter_amount) {
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
|
||||
mscounter += jitter_amount;
|
||||
}
|
||||
|
||||
cooling = false;
|
||||
seconds = 0;
|
||||
}
|
||||
} else {
|
||||
if (++seconds >= field_timetx.value()) // Start cooling period:
|
||||
{
|
||||
transmitter_model.disable();
|
||||
button_transmit.set_text("PAUSED");
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
|
||||
int32_t jitter_amount = field_jitter.value();
|
||||
if (jitter_amount) {
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
|
||||
mscounter += jitter_amount;
|
||||
}
|
||||
|
||||
cooling = true;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::load_list() {
|
||||
freq_list.clear();
|
||||
|
||||
auto open_view = nav_.push<FileLoadView>(".PHOP");
|
||||
open_view->push_dir(hopper_dir);
|
||||
open_view->on_changed = [this](std::filesystem::path path) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "Can't open.");
|
||||
update_freq_list_menu_view();
|
||||
return;
|
||||
}
|
||||
|
||||
freq_list.clear();
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
rf::Frequency freq;
|
||||
if (parse_int(line, freq)) {
|
||||
if (freq_list.size() >= JAMMER_MAX_CH) {
|
||||
break;
|
||||
}
|
||||
freq_list.push_back(freq);
|
||||
}
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
update_freq_list_menu_view();
|
||||
}
|
||||
|
||||
void HopperView::save_list() {
|
||||
if (freq_list.empty()) {
|
||||
nav_.display_modal("Err", "Nothing to save");
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_directory(hopper_dir);
|
||||
|
||||
filename_buffer = "";
|
||||
text_prompt(
|
||||
nav_,
|
||||
filename_buffer,
|
||||
64,
|
||||
[this](std::string& value) {
|
||||
auto path = hopper_dir / (value + ".PHOP");
|
||||
|
||||
File f;
|
||||
auto error = f.create(path);
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "Create fail.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& freq : freq_list) {
|
||||
auto freq_str = to_string_dec_uint(freq) + "\n";
|
||||
f.write(freq_str.c_str(), freq_str.length());
|
||||
}
|
||||
|
||||
text_range_number.set("Saved: " + path.filename().string());
|
||||
});
|
||||
}
|
||||
|
||||
HopperView::HopperView(
|
||||
NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&menu_freq_list,
|
||||
&button_load_list,
|
||||
&button_add_freq,
|
||||
&button_delete_freq,
|
||||
&button_save_list,
|
||||
&button_clear,
|
||||
&labels,
|
||||
&options_type,
|
||||
&text_range_number,
|
||||
&text_range_total,
|
||||
&options_speed,
|
||||
&options_hop,
|
||||
&field_timetx,
|
||||
&field_timepause,
|
||||
&field_jitter,
|
||||
&field_gain,
|
||||
&field_amp,
|
||||
&button_transmit});
|
||||
|
||||
options_type.set_selected_index(3); // Rand CW
|
||||
options_speed.set_selected_index(3); // 10kHz
|
||||
options_hop.set_selected_index(1); // 50ms
|
||||
button_transmit.set_style(&style_val);
|
||||
|
||||
field_timetx.set_value(30);
|
||||
field_timepause.set_value(1);
|
||||
field_gain.set_value(transmitter_model.tx_gain());
|
||||
field_amp.set_value(transmitter_model.rf_amp());
|
||||
|
||||
button_load_list.on_select = [this]() {
|
||||
load_list();
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_add_freq.on_select = [this]() {
|
||||
if (freq_list.size() < JAMMER_MAX_CH) {
|
||||
auto kb_view = nav_.push<FrequencyKeypadView>(0);
|
||||
kb_view->on_changed = [this](rf::Frequency freq) {
|
||||
freq_list.push_back(freq);
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
} else {
|
||||
nav_.display_modal("Err", "No more.");
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_delete_freq.on_select = [this]() {
|
||||
auto i = menu_freq_list.highlighted_index();
|
||||
if (i < freq_list.size()) {
|
||||
freq_list.erase(freq_list.begin() + i);
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_save_list.on_select = [this]() {
|
||||
save_list();
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_clear.on_select = [this]() {
|
||||
// clang-format off
|
||||
nav_.display_modal("Del:", "Clean all?\n", YESNO, [this](bool choice) {
|
||||
if (choice){
|
||||
freq_list.clear();
|
||||
update_freq_list_menu_view();
|
||||
} }, TRUE);
|
||||
// clang-format on
|
||||
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_transmit.on_select = [this](Button&) {
|
||||
if (jamming || cooling)
|
||||
stop_tx();
|
||||
else
|
||||
start_tx();
|
||||
};
|
||||
|
||||
menu_freq_list.on_left = [this]() {
|
||||
button_load_list.focus();
|
||||
};
|
||||
|
||||
menu_freq_list.on_right = [this]() {
|
||||
button_load_list.focus();
|
||||
};
|
||||
|
||||
update_freq_list_menu_view();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::hopper
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2020 euquiq
|
||||
* copyleft mr.r0b0t from the F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "message.hpp"
|
||||
#include "jammer.hpp"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
using namespace jammer;
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
|
||||
class HopperView : public View {
|
||||
public:
|
||||
HopperView(NavigationView& nav);
|
||||
~HopperView();
|
||||
|
||||
HopperView(const HopperView&) = delete;
|
||||
HopperView(HopperView&&) = delete;
|
||||
HopperView& operator=(const HopperView&) = delete;
|
||||
HopperView& operator=(HopperView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
void update_freq_list_menu_view();
|
||||
|
||||
std::string title() const override { return "Hopper"; };
|
||||
|
||||
std::vector<rf::Frequency> freq_list{315000000, 433920000};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
3500000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
|
||||
void start_tx();
|
||||
void on_timer();
|
||||
void stop_tx();
|
||||
void set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
|
||||
void on_retune(const rf::Frequency freq, const uint32_t range);
|
||||
void load_list();
|
||||
void save_list();
|
||||
void on_file_save_write(const std::string& value);
|
||||
|
||||
HopperChannel* hopper_channels = (HopperChannel*)shared_memory.bb_data.data;
|
||||
bool jamming{false};
|
||||
bool cooling{false}; // euquiq: Indicates jammer in cooldown
|
||||
uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
|
||||
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
|
||||
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
|
||||
|
||||
std::string filename_buffer = "";
|
||||
|
||||
const Style& style_val = *Theme::getInstance()->fg_green;
|
||||
const Style& style_cancel = *Theme::getInstance()->fg_red;
|
||||
|
||||
MenuView menu_freq_list{
|
||||
{0, 0, screen_width, 8 * 16},
|
||||
false};
|
||||
|
||||
NewButton button_load_list{
|
||||
{0 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_load,
|
||||
Color::dark_blue(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_save_list{
|
||||
{4 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_save,
|
||||
Color::dark_blue(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_add_freq{
|
||||
{8 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_add,
|
||||
Color::dark_green(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_delete_freq{
|
||||
{12 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_trash,
|
||||
Color::dark_red(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_clear{
|
||||
{screen_width - 4 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_tools_wipesd,
|
||||
Color::red(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 23 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
|
||||
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
|
||||
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 35 * 8}, "Gain:", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 35 * 8}, "A:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField options_type{
|
||||
{7 * 8, 23 * 8},
|
||||
8,
|
||||
{
|
||||
{"Rand FSK", 0},
|
||||
{"FM tone", 1},
|
||||
{"CW sweep", 2},
|
||||
{"Rand CW", 3},
|
||||
}};
|
||||
|
||||
Text text_range_number{
|
||||
{16 * 8, 23 * 8, 2 * 8, 16},
|
||||
"--"};
|
||||
Text text_range_total{
|
||||
{18 * 8, 23 * 8, 3 * 8, 16},
|
||||
"/--"};
|
||||
|
||||
OptionsField options_speed{
|
||||
{7 * 8, 25 * 8},
|
||||
6,
|
||||
{{"10Hz ", 10},
|
||||
{"100Hz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
{"10kHz ", 10000},
|
||||
{"100kHz", 100000}}};
|
||||
|
||||
OptionsField options_hop{
|
||||
{7 * 8, 27 * 8},
|
||||
5,
|
||||
{{"10ms ", 1},
|
||||
{"50ms ", 5},
|
||||
{"100ms", 10},
|
||||
{"1s ", 100},
|
||||
{"2s ", 200},
|
||||
{"5s ", 500},
|
||||
{"10s ", 1000}}};
|
||||
|
||||
NumberField field_timetx{
|
||||
{7 * 8, 29 * 8},
|
||||
3,
|
||||
{1, 180},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_timepause{
|
||||
{8 * 8, 31 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_jitter{
|
||||
{8 * 8, 33 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_gain{
|
||||
{8 * 8, 35 * 8},
|
||||
2,
|
||||
{0, 47},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_amp{
|
||||
{13 * 8, 35 * 8},
|
||||
1,
|
||||
{0, 1},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_transmit{
|
||||
{148, 216, 80, 80},
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
|
||||
MessageHandlerRegistration message_handler_retune{
|
||||
Message::ID::Retune,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const RetuneMessage*>(p);
|
||||
this->on_retune(message->freq, message->range);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::hopper
|
||||
@@ -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<MetronomeView>();
|
||||
}
|
||||
} // 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
|
||||
};
|
||||
}
|
||||
@@ -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<MetronomeView*>(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
|
||||
@@ -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__*/
|
||||
+2
-3
@@ -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) {
|
||||
|
||||
+7
-2
@@ -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},
|
||||
|
||||
+155
-1
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -178,7 +180,8 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
&label_waveform,
|
||||
&waveform,
|
||||
&button_open,
|
||||
&button_save});
|
||||
&button_save,
|
||||
&button_bug_key});
|
||||
|
||||
// Initialize default values for controls
|
||||
field_symbol_rate.set_value(100);
|
||||
@@ -267,6 +270,16 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
});
|
||||
};
|
||||
|
||||
button_bug_key.on_select = [&](Button&) {
|
||||
auto bug_key_input_view = nav_.push<OOKEditorBugKeyView>(ook_data.payload);
|
||||
|
||||
bug_key_input_view->on_save = [this](std::string p) {
|
||||
ook_data.payload = p;
|
||||
text_payload.set(ook_data.payload);
|
||||
draw_waveform();
|
||||
};
|
||||
};
|
||||
|
||||
// Configure button to start or stop the transmission
|
||||
button_send_stop.on_select = [this](Button&) {
|
||||
if (!is_transmitting) {
|
||||
@@ -280,4 +293,145 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
// initial waveform drawing (should be a single line)
|
||||
draw_waveform();
|
||||
}
|
||||
|
||||
/*************** bug key view ****************/
|
||||
|
||||
OOKEditorBugKeyView::OOKEditorBugKeyView(NavigationView& nav, std::string payload)
|
||||
: nav_{nav},
|
||||
payload_{payload} {
|
||||
add_children({&labels,
|
||||
&field_primary_step,
|
||||
&field_secondary_step,
|
||||
&console,
|
||||
&button_insert_high_level_long,
|
||||
&button_insert_high_level_short,
|
||||
&button_insert_low_level_long,
|
||||
&button_insert_low_level_short,
|
||||
&button_delete,
|
||||
&button_save});
|
||||
|
||||
button_insert_low_level_short.on_select = [this](Button&) {
|
||||
on_insert(InsertType::LOW_LEVEL_SHORT);
|
||||
};
|
||||
|
||||
button_insert_low_level_long.on_select = [this](Button&) {
|
||||
on_insert(InsertType::LOW_LEVEL_LONG);
|
||||
};
|
||||
|
||||
button_insert_high_level_short.on_select = [this](Button&) {
|
||||
on_insert(InsertType::HIGH_LEVEL_SHORT);
|
||||
};
|
||||
|
||||
button_insert_high_level_long.on_select = [this](Button&) {
|
||||
on_insert(InsertType::HIGH_LEVEL_LONG);
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save) on_save(build_payload());
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
auto update_step_buttons = [this](int32_t value, Button& btnLow, Button& btnHigh) {
|
||||
std::string low_level_btn_str;
|
||||
std::string high_level_btn_str;
|
||||
if (value <= 14) { // the button width allow max 14 chars
|
||||
for (int i = 0; i < value; i++) {
|
||||
low_level_btn_str.push_back('0');
|
||||
high_level_btn_str.push_back('1');
|
||||
}
|
||||
} else {
|
||||
low_level_btn_str = to_string_dec_int(value) + " * \"0\"";
|
||||
high_level_btn_str = to_string_dec_int(value) + " * \"1\"";
|
||||
}
|
||||
btnLow.set_text(" "); // set_dirty broken console. this is work around
|
||||
btnHigh.set_text(" ");
|
||||
btnLow.set_text(low_level_btn_str);
|
||||
btnHigh.set_text(high_level_btn_str);
|
||||
};
|
||||
|
||||
field_primary_step.on_change = [&](int32_t) {
|
||||
update_step_buttons(field_primary_step.value(),
|
||||
button_insert_low_level_short,
|
||||
button_insert_high_level_short);
|
||||
update_console();
|
||||
};
|
||||
|
||||
field_secondary_step.on_change = [&](int32_t) {
|
||||
update_step_buttons(field_secondary_step.value(),
|
||||
button_insert_low_level_long,
|
||||
button_insert_high_level_long);
|
||||
update_console();
|
||||
};
|
||||
|
||||
field_primary_step.set_value(1);
|
||||
field_secondary_step.set_value(2);
|
||||
update_step_buttons(field_primary_step.value(),
|
||||
button_insert_low_level_short,
|
||||
button_insert_high_level_short);
|
||||
update_console();
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::on_insert(InsertType type) {
|
||||
auto promise_length = 0;
|
||||
std::string promose_level = "0";
|
||||
switch (type) {
|
||||
case InsertType::LOW_LEVEL_SHORT:
|
||||
promise_length = field_primary_step.value();
|
||||
promose_level = "0";
|
||||
break;
|
||||
case InsertType::LOW_LEVEL_LONG:
|
||||
promise_length = field_secondary_step.value();
|
||||
promose_level = "0";
|
||||
break;
|
||||
case InsertType::HIGH_LEVEL_SHORT:
|
||||
promise_length = field_primary_step.value();
|
||||
promose_level = "1";
|
||||
break;
|
||||
case InsertType::HIGH_LEVEL_LONG:
|
||||
promise_length = field_secondary_step.value();
|
||||
promose_level = "1";
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < promise_length; i++) {
|
||||
payload_ += promose_level;
|
||||
}
|
||||
|
||||
update_console();
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::on_delete() {
|
||||
// I'm aware that if user inputted like: [long high][long high][short high], this will delete a pile of them
|
||||
// but this doesnt matter because:
|
||||
// 1. they should not do it, high or low shoudl cross each other, don't repeat
|
||||
// 2. don't have too much RAM to trach the input trace
|
||||
if (payload_.length() > 0) {
|
||||
size_t len = payload_.length();
|
||||
char last_char = payload_[len - 1];
|
||||
size_t pos = len - 1;
|
||||
while (pos > 0 && payload_[pos - 1] == last_char) {
|
||||
pos--;
|
||||
}
|
||||
payload_.erase(pos);
|
||||
update_console();
|
||||
}
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::update_console() {
|
||||
console.clear(true);
|
||||
console.write(payload_);
|
||||
}
|
||||
|
||||
std::string OOKEditorBugKeyView::build_payload() {
|
||||
return payload_;
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::ook_editor
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -29,6 +31,8 @@ namespace ui::external_app::ook_editor {
|
||||
#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
|
||||
#define WAVEFORM_BUFFER_SIZE 550
|
||||
|
||||
/*****************Editor View ******************/
|
||||
|
||||
class OOKEditorAppView : public View {
|
||||
public:
|
||||
void focus() override;
|
||||
@@ -123,6 +127,7 @@ class OOKEditorAppView : public View {
|
||||
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
|
||||
Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]};
|
||||
Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]};
|
||||
Button button_bug_key{{0, 125 + 28 + 3, screen_width, 28}, "Bug Key"};
|
||||
Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]};
|
||||
|
||||
// Progress bar to display transmission progress.
|
||||
@@ -132,6 +137,82 @@ class OOKEditorAppView : public View {
|
||||
Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
|
||||
};
|
||||
|
||||
/******** bug key input view **********/
|
||||
|
||||
enum InsertType {
|
||||
LOW_LEVEL_SHORT,
|
||||
LOW_LEVEL_LONG,
|
||||
HIGH_LEVEL_SHORT,
|
||||
HIGH_LEVEL_LONG
|
||||
};
|
||||
|
||||
class OOKEditorBugKeyView : public View {
|
||||
public:
|
||||
std::function<void(std::string)> on_save{};
|
||||
|
||||
OOKEditorBugKeyView(NavigationView& nav, std::string payload);
|
||||
|
||||
std::string title() const override { return "Bug.K"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string payload_ = "";
|
||||
std::string path_ = "";
|
||||
uint32_t delay_{0};
|
||||
std::string delay_str{""}; // needed by text_prompt
|
||||
|
||||
void on_insert(InsertType type);
|
||||
void on_delete();
|
||||
void update_console();
|
||||
std::string build_payload();
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Primary Step", Theme::getInstance()->fg_light->foreground},
|
||||
{{(screen_width / 2), 0 * 16}, "Secondary Step", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_primary_step{
|
||||
{0 * 8, 1 * 16},
|
||||
3,
|
||||
{0, 550},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_secondary_step{
|
||||
{(screen_width / 2), 1 * 16},
|
||||
3,
|
||||
{0, 550},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Console console{
|
||||
{0, 3 * 16, screen_width, screen_height - 10 * 16}};
|
||||
|
||||
Button button_insert_low_level_long{
|
||||
{0 * 8, 13 * 16, screen_width / 2, 2 * 16},
|
||||
"00"};
|
||||
|
||||
Button button_insert_low_level_short{
|
||||
{0 * 8, 15 * 16, screen_width / 2, 2 * 16},
|
||||
"0"};
|
||||
|
||||
Button button_insert_high_level_long{
|
||||
{(screen_width / 2), 13 * 16, screen_width / 2, 2 * 16},
|
||||
"11"};
|
||||
|
||||
Button button_insert_high_level_short{
|
||||
{(screen_width / 2), 15 * 16, screen_width / 2, 2 * 16},
|
||||
"1"};
|
||||
|
||||
Button button_delete{
|
||||
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"<Backspace"};
|
||||
|
||||
Button button_save{
|
||||
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
}; // namespace ui::external_app::ook_editor
|
||||
|
||||
#endif /*__UI_OOK_EDITOR_H__*/
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_playlist_editor.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<PlaylistEditorView>();
|
||||
}
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_playlist_editor.application_information"), used)) application_information_t _application_information_playlist_editor = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::playlist_editor::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "PlaylistEdit",
|
||||
/*.bitmap_data = */ {
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0xC3,
|
||||
0x00,
|
||||
0xE0,
|
||||
0xFF,
|
||||
0xEF,
|
||||
0xFF,
|
||||
0xC0,
|
||||
0x00,
|
||||
0x83,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_playlist_editor.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "file_reader.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
|
||||
/*********menu**********/
|
||||
PlaylistEditorView::PlaylistEditorView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
portapack::async_tx_enabled = true;
|
||||
add_children({&labels,
|
||||
&button_new,
|
||||
&text_current_ppl_file,
|
||||
&menu_view,
|
||||
&text_hint,
|
||||
&button_open_playlist,
|
||||
&button_edit,
|
||||
&button_insert,
|
||||
&button_save_playlist});
|
||||
|
||||
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
text_hint.set("Edit:" +
|
||||
playlist[menu_view.highlighted_index()].substr(playlist[menu_view.highlighted_index()].find_last_of('/') + 1,
|
||||
playlist[menu_view.highlighted_index()].find(',') -
|
||||
playlist[menu_view.highlighted_index()].find_last_of('/') - 1));
|
||||
};
|
||||
|
||||
button_new.on_select = [this](Button&) {
|
||||
if (on_create_ppl()) {
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
|
||||
refresh_interface();
|
||||
}
|
||||
};
|
||||
|
||||
button_open_playlist.on_select = [this](Button&) {
|
||||
open_file();
|
||||
};
|
||||
|
||||
button_edit.on_select = [this](Button&) {
|
||||
on_edit_item();
|
||||
};
|
||||
|
||||
button_insert.on_select = [this](Button&) {
|
||||
on_insert_item();
|
||||
};
|
||||
|
||||
button_save_playlist.on_select = [this](Button&) {
|
||||
save_ppl();
|
||||
};
|
||||
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON);
|
||||
}
|
||||
|
||||
void PlaylistEditorView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
void PlaylistEditorView::open_file() {
|
||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||
open_view->push_dir(playlist_dir);
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
current_ppl_path = new_file_path;
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::swap_opened_file_or_new_button(DisplayFilenameOrNewButton d) {
|
||||
if (d == DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON) {
|
||||
button_new.hidden(false);
|
||||
text_current_ppl_file.hidden(true);
|
||||
} else {
|
||||
button_new.hidden(true);
|
||||
text_current_ppl_file.hidden(false);
|
||||
text_current_ppl_file.set(current_ppl_name_buffer);
|
||||
}
|
||||
refresh_interface();
|
||||
}
|
||||
|
||||
/*
|
||||
NB: same name would became as "open file"
|
||||
*/
|
||||
bool PlaylistEditorView::on_create_ppl() {
|
||||
bool success = false;
|
||||
text_prompt(
|
||||
nav_,
|
||||
current_ppl_name_buffer,
|
||||
100,
|
||||
[&](std::string& s) {
|
||||
current_ppl_name_buffer = s;
|
||||
|
||||
success = true;
|
||||
current_ppl_name_buffer += ".PPL";
|
||||
current_ppl_path = playlist_dir / std::filesystem::path(current_ppl_name_buffer);
|
||||
|
||||
File f;
|
||||
f.open(current_ppl_path, true, true); // prob safer here as standalone obj as read only and then open again in process func
|
||||
f.close();
|
||||
on_file_changed(current_ppl_path);
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_file_changed(const fs::path& new_file_path) {
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(new_file_path.string());
|
||||
|
||||
if (error) return;
|
||||
|
||||
menu_view.clear();
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
playlist.push_back(line);
|
||||
}
|
||||
|
||||
for (auto& line : playlist) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") {
|
||||
playlist.erase(std::remove(playlist.begin(), playlist.end(), line), playlist.end());
|
||||
}
|
||||
|
||||
// remove line end \n etc
|
||||
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
}
|
||||
text_hint.set("Highlight an entry");
|
||||
|
||||
text_current_ppl_file.set(new_file_path.string());
|
||||
|
||||
ever_opened = true;
|
||||
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
|
||||
|
||||
refresh_menu_view();
|
||||
}
|
||||
|
||||
void PlaylistEditorView::refresh_menu_view() {
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& line : playlist) {
|
||||
if (line.length() == 0 || line[0] == '#') {
|
||||
menu_view.add_item({line,
|
||||
ui::Color::grey(),
|
||||
&bitmap_icon_notepad,
|
||||
[this](KeyEvent) {
|
||||
button_insert.focus();
|
||||
}});
|
||||
} else {
|
||||
const auto filename = line.substr(line.find_last_of('/') + 1, line.find(',') - line.find_last_of('/') - 1);
|
||||
menu_view.add_item({filename,
|
||||
ui::Color::white(),
|
||||
&bitmap_icon_cwgen,
|
||||
[this](KeyEvent) {
|
||||
button_edit.focus();
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_edit_item() {
|
||||
if (!ever_opened || playlist.empty()) {
|
||||
nav_.display_modal("Err", "No entry");
|
||||
return;
|
||||
}
|
||||
auto edit_view = nav_.push<PlaylistItemEditView>(
|
||||
playlist[menu_view.highlighted_index()]);
|
||||
|
||||
edit_view->set_on_delete([this]() {
|
||||
playlist.erase(playlist.begin() + menu_view.highlighted_index());
|
||||
refresh_interface();
|
||||
});
|
||||
|
||||
edit_view->on_save = [this](std::string new_item) {
|
||||
playlist[menu_view.highlighted_index()] = new_item;
|
||||
refresh_interface();
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_insert_item() {
|
||||
// if (current_ppl_path.empty() || current_ppl_path.string().find_first_not_of(" \t\n\r") == std::string::npos) {
|
||||
if (!ever_opened) { // TODO: this is a workaround because the above line is not working and I took one hour and didn't find the issue
|
||||
nav_.display_modal("Err", "No playlist file loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
auto edit_view = nav_.push<PlaylistItemEditView>(
|
||||
"");
|
||||
|
||||
edit_view->on_save = [&](std::string new_item) {
|
||||
if (playlist.empty()) {
|
||||
playlist.push_back(new_item);
|
||||
} else {
|
||||
playlist.insert(playlist.begin() + menu_view.highlighted_index() + 1, new_item);
|
||||
}
|
||||
refresh_interface();
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::refresh_interface() {
|
||||
const auto previous_index = menu_view.highlighted_index();
|
||||
refresh_menu_view();
|
||||
set_dirty();
|
||||
menu_view.set_highlighted(previous_index);
|
||||
}
|
||||
|
||||
void PlaylistEditorView::save_ppl() {
|
||||
if (current_ppl_path.empty()) {
|
||||
nav_.display_modal("Err", "No playlist file loaded");
|
||||
return;
|
||||
} else if (playlist.empty()) {
|
||||
nav_.display_modal("Err", "List is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(current_ppl_path.string(), false, false);
|
||||
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "open err");
|
||||
return;
|
||||
}
|
||||
|
||||
// clear file
|
||||
playlist_file.seek(0);
|
||||
playlist_file.truncate();
|
||||
|
||||
// write new data
|
||||
for (const auto& entry : playlist) {
|
||||
playlist_file.write_line(entry);
|
||||
}
|
||||
|
||||
nav_.display_modal("Save", "Saved playlist\n" + current_ppl_path.string());
|
||||
}
|
||||
|
||||
/*********edit**********/
|
||||
|
||||
PlaylistItemEditView::PlaylistItemEditView(
|
||||
NavigationView& nav,
|
||||
std::string item)
|
||||
: nav_{nav},
|
||||
original_item_{item} {
|
||||
add_children({&labels,
|
||||
&field_path,
|
||||
&field_delay,
|
||||
&button_browse,
|
||||
&button_input_delay,
|
||||
&button_delete,
|
||||
&button_save});
|
||||
|
||||
button_browse.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C16");
|
||||
open_view->push_dir(captures_dir);
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
field_path.set_text(path.string());
|
||||
path_ = path.string();
|
||||
};
|
||||
field_delay.on_change = [&](auto) {
|
||||
delay_ = field_delay.value();
|
||||
};
|
||||
};
|
||||
|
||||
button_input_delay.on_select = [this](Button&) {
|
||||
delay_str = to_string_dec_uint(delay_);
|
||||
if (delay_str == "0") {
|
||||
delay_str = "";
|
||||
}
|
||||
text_prompt(
|
||||
nav_,
|
||||
delay_str,
|
||||
100,
|
||||
[&](std::string& s) {
|
||||
delay_ = atoi(s.c_str());
|
||||
field_delay.set_value(delay_);
|
||||
refresh_ui();
|
||||
});
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
if (on_delete) on_delete();
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_save.on_select = [&](Button&) {
|
||||
if (path_.empty()) {
|
||||
nav_.display_modal("Err", "Select a file\n or press back to cancel");
|
||||
return;
|
||||
}
|
||||
if (on_save) on_save(build_item());
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
if (!on_delete) {
|
||||
button_delete.hidden(true);
|
||||
}
|
||||
|
||||
parse_item(item);
|
||||
refresh_ui();
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::refresh_ui() {
|
||||
field_path.set_text(path_);
|
||||
field_delay.set_value(delay_);
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::parse_item(std::string item) {
|
||||
// Parse format: path,delay
|
||||
if (item.empty()) {
|
||||
return;
|
||||
}
|
||||
auto parts = split_string(item, ',');
|
||||
if (parts.size() >= 1) {
|
||||
path_ = std::string{parts[0]};
|
||||
}
|
||||
if (parts.size() >= 2) {
|
||||
delay_ = atoi(std::string{parts[1]}.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string PlaylistItemEditView::build_item() const {
|
||||
const auto v = path_ + "," + to_string_dec_uint(field_delay.value());
|
||||
return path_ + "," + to_string_dec_uint(field_delay.value());
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_PLAYLIST_EDITOR_H__
|
||||
#define __UI_PLAYLIST_EDITOR_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
|
||||
enum DisplayFilenameOrNewButton {
|
||||
DISPLAY_FILENAME,
|
||||
DISPLAY_NEW_BUTTON
|
||||
};
|
||||
|
||||
class PlaylistEditorView : public View {
|
||||
public:
|
||||
PlaylistEditorView(NavigationView& nav);
|
||||
std::string title() const override { return "PPL Edit"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::vector<std::string> playlist = {};
|
||||
fs::path current_ppl_path = "";
|
||||
std::string current_ppl_name_buffer = ""; // this is because text_prompt needs it. TODO: this is so annoying, shoudl refactor that func
|
||||
bool ever_opened = false;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "PPL file:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_new{
|
||||
{(sizeof("PPL file:") + 1) * 8, 0 * 16, 8 * 5, 16},
|
||||
"New"};
|
||||
|
||||
Text text_current_ppl_file{
|
||||
{sizeof("PPL file:") * 8, 0 * 16, screen_width - sizeof("PPL file:") * 8, 16},
|
||||
""};
|
||||
|
||||
MenuView menu_view{};
|
||||
|
||||
Text text_hint{
|
||||
{0, 27 * 8, screen_width, 16},
|
||||
"Open a PPL file"};
|
||||
|
||||
Text text_ppl_name{
|
||||
{0, 27 * 8, screen_width, 16},
|
||||
"Highlight an app"};
|
||||
|
||||
Button button_open_playlist{
|
||||
{0, 29 * 8, screen_width / 2 - 1, 32},
|
||||
"Open PPL"};
|
||||
|
||||
Button button_edit{
|
||||
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
|
||||
"Edit Item"};
|
||||
|
||||
Button button_insert{
|
||||
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
|
||||
"Ins. After"};
|
||||
|
||||
Button button_save_playlist{
|
||||
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
|
||||
"Save PPL"};
|
||||
|
||||
void open_file();
|
||||
void swap_opened_file_or_new_button(DisplayFilenameOrNewButton d);
|
||||
void on_file_changed(const fs::path& path);
|
||||
bool on_create_ppl();
|
||||
void refresh_interface();
|
||||
void on_edit_item();
|
||||
void on_insert_item();
|
||||
void refresh_menu_view();
|
||||
void save_ppl();
|
||||
};
|
||||
|
||||
class PlaylistItemEditView : public View {
|
||||
public:
|
||||
std::function<void(std::string)> on_save{};
|
||||
std::function<void()> on_delete{};
|
||||
|
||||
PlaylistItemEditView(NavigationView& nav, std::string item);
|
||||
|
||||
std::string title() const override { return "Edit Item"; };
|
||||
void focus() override;
|
||||
|
||||
void set_on_delete(std::function<void()> callback) {
|
||||
on_delete = callback;
|
||||
button_delete.hidden(false);
|
||||
}
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string original_item_ = "";
|
||||
std::string path_ = "";
|
||||
uint32_t delay_{0};
|
||||
std::string delay_str{""}; // needed by text_prompt
|
||||
|
||||
void refresh_ui();
|
||||
void parse_item(std::string item);
|
||||
std::string build_item() const;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Path:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Delay(ms):", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
TextField field_path{
|
||||
{0, 2 * 16, screen_width, 16},
|
||||
"empty"};
|
||||
|
||||
NumberField field_delay{
|
||||
{11 * 8, 5 * 16},
|
||||
5,
|
||||
{0, 99999},
|
||||
10,
|
||||
' '};
|
||||
|
||||
Button button_browse{
|
||||
{2 * 8, 8 * 16, 8 * 8, 3 * 16},
|
||||
"Browse"};
|
||||
|
||||
Button button_input_delay{
|
||||
{12 * 8, 8 * 16, sizeof("Input Delay") * 8, 3 * 16},
|
||||
"Input Delay"};
|
||||
|
||||
Button button_delete{
|
||||
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Delete"};
|
||||
|
||||
Button button_save{
|
||||
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
|
||||
#endif // __UI_PLAYLIST_EDITOR_H__
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_sd_wipe.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<WipeSDView>();
|
||||
}
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_sd_wipe.application_information"), used)) application_information_t _application_information_sd_wipe = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::sd_wipe::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Wipe SD card",
|
||||
/*.bitmap_data = */ {0xF0, 0x3F, 0x58, 0x35, 0x5C, 0x35, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0x3C, 0x1C, 0xBC, 0xC9, 0xBC, 0xE3, 0x2C, 0x77, 0x5C, 0x3E, 0xAC, 0x1C, 0x5C, 0x3E, 0x2C, 0x77, 0x9C, 0xE3, 0xAC, 0xC1},
|
||||
/*.icon_color = */ ui::Color::red().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+4
-2
@@ -22,7 +22,9 @@
|
||||
|
||||
#include "ui_sd_wipe.hpp"
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
|
||||
Thread* WipeSDView::thread{nullptr};
|
||||
|
||||
@@ -63,4 +65,4 @@ void WipeSDView::focus() {
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
Vendored
+4
-2
@@ -30,7 +30,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
|
||||
class WipeSDView : public View {
|
||||
public:
|
||||
@@ -87,6 +89,6 @@ class WipeSDView : public View {
|
||||
""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
|
||||
#endif /*__UI_SD_WIPE_H__*/
|
||||
+83
@@ -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<TunerView>();
|
||||
}
|
||||
} // 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
|
||||
};
|
||||
}
|
||||
+256
@@ -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<OptionsField::option_t> 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
|
||||
+145
@@ -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<std::string, NoteInfo> 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<std::string, Instrument> instruments{
|
||||
{"Guitar", guitar},
|
||||
{"Violin", violin},
|
||||
{"Pitch Fork", pitch_fork}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::tuner
|
||||
|
||||
#endif /*__UI_TUNER_H__*/
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::view_wav {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<ViewWavView>();
|
||||
}
|
||||
} // namespace ui::external_app::view_wav
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_view_wav.application_information"), used)) application_information_t _application_information_view_wav = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::view_wav::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "WAV Viewer",
|
||||
/*.bitmap_data = */ {0xF0, 0x0F, 0x1C, 0x18, 0x17, 0x38, 0x15, 0x78, 0x15, 0xF8, 0x15, 0x82, 0x15, 0x8B, 0xD5, 0x83, 0xD5, 0xBB, 0xD5, 0x83, 0x15, 0x8B, 0x15, 0x92, 0x15, 0xA0, 0x17, 0x80, 0x1C, 0x80, 0xF0, 0xFF},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'T', 'X'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+3
-2
@@ -28,8 +28,9 @@
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::view_wav {
|
||||
|
||||
void ViewWavView::update_scale(int32_t new_scale) {
|
||||
scale = new_scale;
|
||||
@@ -345,4 +346,4 @@ ViewWavView::~ViewWavView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::view_wav */
|
||||
Vendored
+4
-2
@@ -28,7 +28,9 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::view_wav {
|
||||
|
||||
class ViewWavView : public View {
|
||||
public:
|
||||
@@ -202,4 +204,4 @@ class ViewWavView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::view_wav */
|
||||
@@ -50,6 +50,10 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @param read_only: open in readonly mode
|
||||
* @param create: create if it doesnt exist
|
||||
*/
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
|
||||
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
|
||||
if (create)
|
||||
|
||||
@@ -49,3 +49,5 @@ const std::filesystem::path sstv_dir = u"SSTV";
|
||||
const std::filesystem::path wav_dir = u"WAV";
|
||||
const std::filesystem::path whipcalc_dir = u"WHIPCALC";
|
||||
const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
|
||||
@@ -51,5 +51,7 @@ extern const std::filesystem::path sstv_dir;
|
||||
extern const std::filesystem::path wav_dir;
|
||||
extern const std::filesystem::path whipcalc_dir;
|
||||
extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -66,6 +66,11 @@ int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
if (direction == prev_direction) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
|
||||
// false: normal, true: reverse
|
||||
if (portapack::persistent_memory::encoder_dial_direction())
|
||||
direction = -direction;
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,17 +38,31 @@ BtnGridView::BtnGridView(
|
||||
set_parent_rect(new_parent_rect);
|
||||
set_focusable(true);
|
||||
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
this->on_tick_second();
|
||||
button_pgup.set_focusable(false);
|
||||
button_pgup.on_select = [this](Button&) {
|
||||
if (arrow_up_enabled) {
|
||||
if (((int64_t)highlighted_item - displayed_max) > 0)
|
||||
set_highlighted(highlighted_item - displayed_max);
|
||||
else
|
||||
set_highlighted(0);
|
||||
}
|
||||
};
|
||||
|
||||
add_child(&arrow_more);
|
||||
arrow_more.set_focusable(false);
|
||||
arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background);
|
||||
button_pgdown.set_focusable(false);
|
||||
button_pgdown.on_select = [this](Button&) {
|
||||
if (arrow_down_enabled) {
|
||||
set_highlighted(highlighted_item + displayed_max);
|
||||
}
|
||||
};
|
||||
|
||||
button_pgup.set_style(Theme::getInstance()->bg_darkest_small);
|
||||
button_pgdown.set_style(Theme::getInstance()->bg_darkest_small);
|
||||
|
||||
add_child(&button_pgup);
|
||||
add_child(&button_pgdown);
|
||||
}
|
||||
|
||||
BtnGridView::~BtnGridView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
}
|
||||
|
||||
void BtnGridView::set_max_rows(int rows) {
|
||||
@@ -63,7 +77,10 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
displayed_max = (parent_rect().size().height() / button_h);
|
||||
arrow_more.set_parent_rect({228, (Coord)(displayed_max * button_h), 8, 8});
|
||||
|
||||
button_pgup.set_parent_rect({0, (Coord)(displayed_max * button_h), 120, 16});
|
||||
button_pgdown.set_parent_rect({120, (Coord)(displayed_max * button_h), 120, 16});
|
||||
|
||||
displayed_max *= rows_;
|
||||
|
||||
// Delete any existing buttons.
|
||||
@@ -84,28 +101,40 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
menu_item_views.push_back(std::move(item));
|
||||
}
|
||||
|
||||
update_items();
|
||||
}
|
||||
|
||||
void BtnGridView::set_arrow_enabled(bool enabled) {
|
||||
void BtnGridView::set_arrow_up_enabled(bool enabled) {
|
||||
if (!show_arrows)
|
||||
return;
|
||||
if (enabled) {
|
||||
add_child(&arrow_more);
|
||||
} else {
|
||||
remove_child(&arrow_more);
|
||||
if (!arrow_up_enabled) {
|
||||
arrow_up_enabled = true;
|
||||
button_pgup.set_text("< PREV");
|
||||
}
|
||||
} else if (!enabled) {
|
||||
if (arrow_up_enabled) {
|
||||
arrow_up_enabled = false;
|
||||
button_pgup.set_text(" ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void BtnGridView::on_tick_second() {
|
||||
if (more && blink)
|
||||
arrow_more.set_foreground(Theme::getInstance()->bg_darkest->foreground);
|
||||
else
|
||||
arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
blink = !blink;
|
||||
|
||||
arrow_more.set_dirty();
|
||||
}
|
||||
void BtnGridView::set_arrow_down_enabled(bool enabled) {
|
||||
if (!show_arrows)
|
||||
return;
|
||||
if (enabled) {
|
||||
if (!arrow_down_enabled) {
|
||||
arrow_down_enabled = true;
|
||||
button_pgdown.set_text("NEXT >");
|
||||
}
|
||||
} else if (!enabled) {
|
||||
if (arrow_down_enabled) {
|
||||
arrow_down_enabled = false;
|
||||
button_pgdown.set_text(" ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void BtnGridView::clear() {
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
@@ -153,15 +182,23 @@ void BtnGridView::insert_item(const GridItem& new_item, size_t position, bool in
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::show_hide_arrows() {
|
||||
if (highlighted_item == 0) {
|
||||
set_arrow_up_enabled(false);
|
||||
} else {
|
||||
set_arrow_up_enabled(true);
|
||||
}
|
||||
if (highlighted_item == (menu_items.size() - 1)) {
|
||||
set_arrow_down_enabled(false);
|
||||
} else {
|
||||
set_arrow_down_enabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::update_items() {
|
||||
size_t i = 0;
|
||||
Color bg_color = portapack::persistent_memory::menu_color();
|
||||
|
||||
if ((menu_items.size()) > (displayed_max + offset)) {
|
||||
more = true;
|
||||
blink = true;
|
||||
} else
|
||||
more = false;
|
||||
Color bg_color = portapack::persistent_memory::menu_color();
|
||||
|
||||
for (auto& item : menu_item_views) {
|
||||
if ((i + offset) >= menu_items.size()) {
|
||||
@@ -189,14 +226,23 @@ NewButton* BtnGridView::item_view(size_t index) const {
|
||||
return menu_item_views[index].get();
|
||||
}
|
||||
|
||||
void BtnGridView::show_arrows_enabled(bool enabled) {
|
||||
show_arrows = enabled;
|
||||
if (!enabled) {
|
||||
remove_child(&button_pgup);
|
||||
remove_child(&button_pgdown);
|
||||
}
|
||||
}
|
||||
|
||||
bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
int32_t item_count = (int32_t)menu_items.size();
|
||||
|
||||
if (new_value < 0)
|
||||
return false;
|
||||
|
||||
if (new_value >= item_count)
|
||||
if (new_value >= item_count) {
|
||||
new_value = item_count - 1;
|
||||
}
|
||||
|
||||
if (((uint32_t)new_value > offset) && ((new_value - offset) >= displayed_max)) {
|
||||
// Shift BtnGridView up
|
||||
@@ -222,6 +268,8 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
if (visible())
|
||||
item_view(highlighted_item - offset)->focus();
|
||||
|
||||
show_hide_arrows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -235,21 +283,22 @@ void BtnGridView::on_focus() {
|
||||
|
||||
void BtnGridView::on_blur() {
|
||||
#if 0
|
||||
if (!keep_highlight)
|
||||
item_view(highlighted_item - offset)->unhighlight();
|
||||
if (!keep_highlight)
|
||||
item_view(highlighted_item - offset)->unhighlight();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BtnGridView::on_show() {
|
||||
on_populate();
|
||||
|
||||
View::on_show();
|
||||
set_highlighted(highlighted_item);
|
||||
}
|
||||
|
||||
void BtnGridView::on_hide() {
|
||||
View::on_hide();
|
||||
|
||||
clear();
|
||||
set_arrow_up_enabled(false);
|
||||
set_arrow_down_enabled(false);
|
||||
}
|
||||
|
||||
bool BtnGridView::on_key(const KeyEvent key) {
|
||||
|
||||
@@ -68,11 +68,18 @@ class BtnGridView : public View {
|
||||
|
||||
NewButton* item_view(size_t index) const;
|
||||
|
||||
bool show_arrows{true}; // flag used to hide arrows in main menu
|
||||
void show_arrows_enabled(bool enabled);
|
||||
|
||||
bool set_highlighted(int32_t new_value);
|
||||
uint32_t highlighted_index();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void set_arrow_enabled(bool enabled);
|
||||
bool arrow_up_enabled{false};
|
||||
bool arrow_down_enabled{false};
|
||||
void set_arrow_up_enabled(bool enabled);
|
||||
void set_arrow_down_enabled(bool enabled);
|
||||
void show_hide_arrows();
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
void on_show() override;
|
||||
@@ -88,24 +95,21 @@ class BtnGridView : public View {
|
||||
|
||||
private:
|
||||
int rows_{3};
|
||||
void on_tick_second();
|
||||
|
||||
bool keep_highlight{false};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
std::vector<GridItem> menu_items{};
|
||||
std::vector<std::unique_ptr<NewButton>> menu_item_views{};
|
||||
|
||||
Image arrow_more{
|
||||
{228, 320 - 8, 8, 8},
|
||||
&bitmap_more,
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
Theme::getInstance()->bg_darkest->background};
|
||||
Button button_pgup{
|
||||
{0, 324, 120, 16},
|
||||
" "};
|
||||
|
||||
Button button_pgdown{
|
||||
{121, 324, 119, 16},
|
||||
" "};
|
||||
|
||||
int button_w = 240 / rows_;
|
||||
static constexpr int button_h = 48;
|
||||
bool blink = false;
|
||||
bool more = false;
|
||||
size_t displayed_max{0};
|
||||
size_t highlighted_item{0};
|
||||
size_t offset{0};
|
||||
|
||||
@@ -64,7 +64,8 @@ class AudioSpectrumView : public View {
|
||||
128,
|
||||
0,
|
||||
false,
|
||||
Theme::getInstance()->bg_darkest->foreground};
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
true};
|
||||
};
|
||||
|
||||
class FrequencyScale : public Widget {
|
||||
|
||||
@@ -67,7 +67,8 @@ class TimeScopeView : public View {
|
||||
128,
|
||||
0,
|
||||
false,
|
||||
Theme::getInstance()->bg_darkest->foreground};
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
true};
|
||||
};
|
||||
|
||||
class TVView : public Widget {
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_scanner.hpp"
|
||||
#include "ui_sd_over_usb.hpp"
|
||||
#include "ui_sd_wipe.hpp"
|
||||
#include "ui_search.hpp"
|
||||
#include "ui_settings.hpp"
|
||||
#include "ui_siggen.hpp"
|
||||
@@ -63,10 +62,8 @@
|
||||
// #include "ui_test.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_touchtunes.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_weatherstation.hpp"
|
||||
#include "ui_subghzd.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_battinfo.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
@@ -125,7 +122,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
@@ -142,7 +139,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"scanner", "Scanner", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
@@ -153,7 +150,6 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
|
||||
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
|
||||
/* TX ********************************************************************/
|
||||
//{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
@@ -163,19 +159,15 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
|
||||
};
|
||||
|
||||
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
|
||||
@@ -310,6 +302,15 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_bias_tee();
|
||||
};
|
||||
|
||||
button_fake_brightness.on_select = [this](ImageButton&) {
|
||||
set_dirty();
|
||||
pmem::toggle_fake_brightness_level();
|
||||
refresh();
|
||||
if (nullptr != parent()) {
|
||||
parent()->set_dirty(); // The parent of NavigationView shal be the SystemView
|
||||
}
|
||||
};
|
||||
|
||||
button_camera.on_select = [this](ImageButton&) {
|
||||
this->on_camera();
|
||||
};
|
||||
@@ -330,6 +331,7 @@ SystemStatusView::SystemStatusView(
|
||||
|
||||
audio::output::stop();
|
||||
audio::output::update_audio_mute();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -349,12 +351,29 @@ void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) {
|
||||
batt_was_inited = true;
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Check if charging state changed to charging
|
||||
static bool was_charging = false;
|
||||
if (msg->on_charger && !was_charging) {
|
||||
// Only show charging modal when transitioning to charging state
|
||||
nav_.display_modal(
|
||||
"CHARGING",
|
||||
"Screen on while charging?",
|
||||
YESNO,
|
||||
[this](bool keep_screen_on) {
|
||||
if (!keep_screen_on) {
|
||||
EventDispatcher::set_display_sleep(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
was_charging = msg->on_charger;
|
||||
|
||||
if (!pmem::ui_hide_numeric_battery()) {
|
||||
battery_text.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
|
||||
}
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
battery_icon.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
@@ -372,6 +391,7 @@ void SystemStatusView::refresh() {
|
||||
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker);
|
||||
|
||||
if (!pmem::ui_hide_fake_brightness() && !pmem::config_lcd_inverted_mode()) status_icons.add(&button_fake_brightness);
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
batt_was_inited = true;
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
@@ -404,6 +424,9 @@ void SystemStatusView::refresh() {
|
||||
button_converter.set_bitmap(pmem::config_updown_converter() ? &bitmap_icon_downconvert : &bitmap_icon_upconvert);
|
||||
button_converter.set_foreground(pmem::config_converter() ? Theme::getInstance()->fg_red->foreground : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
// Fake Brightness
|
||||
button_fake_brightness.set_foreground((pmem::apply_fake_brightness() & (!pmem::config_lcd_inverted_mode())) ? *Theme::getInstance()->status_active : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -746,7 +769,7 @@ void NavigationView::handle_autostart() {
|
||||
|
||||
/* Helpers **************************************************************/
|
||||
|
||||
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
@@ -761,10 +784,10 @@ static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc)
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t notice_pos) {
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos) {
|
||||
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
|
||||
if (externalItems.empty()) {
|
||||
grid.insert_item({"Notice!",
|
||||
grid.insert_item({"ExtAppErr",
|
||||
Theme::getInstance()->error_dark->foreground,
|
||||
nullptr,
|
||||
[&nav]() {
|
||||
@@ -774,7 +797,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to " + apps_dir.string() + " folder of SD card.");
|
||||
}},
|
||||
notice_pos);
|
||||
error_tile_pos);
|
||||
} else {
|
||||
std::sort(externalItems.begin(), externalItems.end(), [](const auto &a, const auto &b)
|
||||
{
|
||||
@@ -795,6 +818,12 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
bool verify_sdcard_format() {
|
||||
FATFS* fs = &sd_card::fs;
|
||||
return (fs->fs_type == FS_FAT32 || fs->fs_type == FS_EXFAT) || !(sd_card::status() == sd_card::Status::Mounted);
|
||||
/* ^ to satisfy those users that not use an sd*/
|
||||
}
|
||||
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav)
|
||||
@@ -805,9 +834,7 @@ void ReceiversMenuView::on_populate() {
|
||||
if (return_icon) {
|
||||
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, RX);
|
||||
|
||||
add_external_items(nav_, app_location_t::RX, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -821,9 +848,7 @@ void TransmittersMenuView::on_populate() {
|
||||
if (return_icon) {
|
||||
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, TX);
|
||||
|
||||
add_external_items(nav_, app_location_t::TX, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -839,9 +864,7 @@ void UtilitiesMenuView::on_populate() {
|
||||
if (return_icon) {
|
||||
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, UTILITIES);
|
||||
|
||||
add_external_items(nav_, app_location_t::UTILITIES, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -862,12 +885,17 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) {
|
||||
SystemMenuView::SystemMenuView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
set_max_rows(2); // allow wider buttons
|
||||
set_arrow_enabled(false);
|
||||
show_arrows_enabled(false);
|
||||
}
|
||||
|
||||
void SystemMenuView::on_populate() {
|
||||
if (!verify_sdcard_format()) {
|
||||
add_item({"SDCard Error", Theme::getInstance()->error_dark->foreground, nullptr, [this]() {
|
||||
nav_.display_modal("Error", "SD Card is not exFAT/FAT32,\nformat to exFAT or FAT32 on PC");
|
||||
}});
|
||||
}
|
||||
add_apps(nav_, *this, HOME);
|
||||
add_external_items(nav_, app_location_t::HOME, *this, 2);
|
||||
add_external_items(nav_, app_location_t::HOME, *this, 0);
|
||||
add_item({"HackRF", Theme::getInstance()->fg_cyan->foreground, &bitmap_icon_hackrf, [this]() { hackrf_mode(nav_); }});
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ using namespace sd_card;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos);
|
||||
bool verify_sdcard_format();
|
||||
|
||||
enum modal_t {
|
||||
INFO = 0,
|
||||
YESNO,
|
||||
@@ -197,6 +201,7 @@ class SystemStatusView : public View {
|
||||
static constexpr auto default_title = "";
|
||||
bool batt_was_inited = false; // if the battery was off on tart, but later turned on.
|
||||
bool batt_info_up = false; // to prevent show multiple batt info dialog
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
@@ -279,6 +284,12 @@ class SystemStatusView : public View {
|
||||
Theme::getInstance()->fg_light->foreground,
|
||||
Theme::getInstance()->bg_dark->background};
|
||||
|
||||
ImageButton button_fake_brightness{
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_brightness,
|
||||
*Theme::getInstance()->status_active,
|
||||
Theme::getInstance()->bg_dark->background};
|
||||
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{0, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
@@ -322,7 +333,7 @@ class InformationView : public View {
|
||||
|
||||
Rectangle backdrop{
|
||||
{0, 0 * 16, 240, 16},
|
||||
{33, 33, 33}};
|
||||
Theme::getInstance()->bg_darker->background};
|
||||
|
||||
Text version{
|
||||
{2, 0, 11 * 8, 16},
|
||||
|
||||
@@ -240,21 +240,16 @@ namespace ui {
|
||||
|
||||
SDCardDebugView::SDCardDebugView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&text_csd_title,
|
||||
&labels,
|
||||
&text_format,
|
||||
&text_csd_value_3,
|
||||
&text_csd_value_2,
|
||||
&text_csd_value_1,
|
||||
&text_csd_value_0,
|
||||
&text_bus_width_title,
|
||||
&text_bus_width_value,
|
||||
&text_card_type_title,
|
||||
&text_card_type_value,
|
||||
&text_block_size_title,
|
||||
&text_block_size_value,
|
||||
&text_block_count_title,
|
||||
&text_block_count_value,
|
||||
&text_capacity_title,
|
||||
&text_capacity_value,
|
||||
&text_test_write_time_title,
|
||||
&text_test_write_time_value,
|
||||
@@ -374,6 +369,14 @@ void SDCardDebugView::on_status(const sd_card::Status) {
|
||||
text_csd_value_1.set(to_string_hex(csd[1], 8));
|
||||
text_csd_value_0.set(to_string_hex(csd[0], 8));
|
||||
|
||||
text_format.set(fetch_sdcard_format());
|
||||
if (fetch_sdcard_format().find("FAT32") != std::string::npos) {
|
||||
// to satisfy the intendent style in this app, the text contains padding space, thus can't use ==
|
||||
text_format.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_format.set_style(Theme::getInstance()->error_dark);
|
||||
}
|
||||
|
||||
BlockDeviceInfo block_device_info;
|
||||
if (sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS) {
|
||||
text_block_size_value.set(to_string_dec_uint(block_device_info.blk_size, 5));
|
||||
@@ -447,4 +450,38 @@ void SDCardDebugView::on_test() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string SDCardDebugView::fetch_sdcard_format() {
|
||||
const size_t max_len = sizeof("Undefined: 255") + 1;
|
||||
|
||||
auto padding = [max_len](std::string s) {
|
||||
if (s.length() < max_len) {
|
||||
return std::string(max_len - s.length(), ' ') + s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
FATFS* fs = &sd_card::fs;
|
||||
std::string resault;
|
||||
|
||||
switch (fs->fs_type) {
|
||||
case FS_FAT12:
|
||||
resault = "FAT12";
|
||||
break;
|
||||
case FS_FAT16:
|
||||
resault = "FAT16";
|
||||
break;
|
||||
case FS_FAT32:
|
||||
resault = "FAT32";
|
||||
break;
|
||||
case FS_EXFAT:
|
||||
resault = "ExFAT";
|
||||
break;
|
||||
default:
|
||||
resault = "Undefined: " + to_string_dec_uint(fs->fs_type, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return padding(resault);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -45,15 +45,23 @@ class SDCardDebugView : public View {
|
||||
|
||||
void on_status(const sd_card::Status status);
|
||||
void on_test();
|
||||
std::string fetch_sdcard_format();
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Format", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "CSD", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Bus width", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Card type", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "Block size", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 9 * 16}, "Block count", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 10 * 16}, "Capacity", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Bus width", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
Text text_title{
|
||||
{(240 - (7 * 8)) / 2, 1 * 16, (7 * 8), 16},
|
||||
"SD Card",
|
||||
};
|
||||
|
||||
Text text_csd_title{
|
||||
{0, 3 * 16, (8 * 8), 16},
|
||||
"CSD",
|
||||
Text text_format{
|
||||
{240 - ((sizeof("Undefined: 255") + 1) * 8), 1 * 16, (sizeof("Undefined: 255") + 1) * 8, 16},
|
||||
"Unknown",
|
||||
};
|
||||
|
||||
Text text_csd_value_3{
|
||||
@@ -78,11 +86,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t bus_width_characters = 1;
|
||||
|
||||
Text text_bus_width_title{
|
||||
{0, 5 * 16, (9 * 8), 16},
|
||||
"Bus width",
|
||||
};
|
||||
|
||||
Text text_bus_width_value{
|
||||
{240 - (bus_width_characters * 8), 5 * 16, (bus_width_characters * 8), 16},
|
||||
"",
|
||||
@@ -90,11 +93,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t card_type_characters = 13;
|
||||
|
||||
Text text_card_type_title{
|
||||
{0, 6 * 16, (9 * 8), 16},
|
||||
"Card type",
|
||||
};
|
||||
|
||||
Text text_card_type_value{
|
||||
{240 - (card_type_characters * 8), 6 * 16, (card_type_characters * 8), 16},
|
||||
"",
|
||||
@@ -102,11 +100,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t block_size_characters = 5;
|
||||
|
||||
Text text_block_size_title{
|
||||
{0, 8 * 16, (10 * 8), 16},
|
||||
"Block size",
|
||||
};
|
||||
|
||||
Text text_block_size_value{
|
||||
{240 - (block_size_characters * 8), 8 * 16, (block_size_characters * 8), 16},
|
||||
"",
|
||||
@@ -114,11 +107,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t block_count_characters = 9;
|
||||
|
||||
Text text_block_count_title{
|
||||
{0, 9 * 16, (11 * 8), 16},
|
||||
"Block count",
|
||||
};
|
||||
|
||||
Text text_block_count_value{
|
||||
{240 - (block_count_characters * 8), 9 * 16, (block_count_characters * 8), 16},
|
||||
"",
|
||||
@@ -126,11 +114,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t capacity_characters = 10;
|
||||
|
||||
Text text_capacity_title{
|
||||
{0, 10 * 16, (8 * 8), 16},
|
||||
"Capacity",
|
||||
};
|
||||
|
||||
Text text_capacity_value{
|
||||
{240 - (capacity_characters * 8), 10 * 16, (capacity_characters * 8), 16},
|
||||
"",
|
||||
|
||||
@@ -271,6 +271,7 @@ macro(DeclareTargets chunk_tag name)
|
||||
project("baseband_${name}")
|
||||
|
||||
include(${RULESPATH}/rules.cmake)
|
||||
set_source_files_properties(${MODE_CPPSRC} PROPERTIES COMPILE_FLAGS "${MODE_FLAGS}")
|
||||
add_executable(${PROJECT_NAME}.elf $<TARGET_OBJECTS:baseband_shared> ${MODE_CPPSRC} ${HALSRC} ${PLATFORMSRC})
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
||||
add_definitions(${DEFS})
|
||||
@@ -313,6 +314,7 @@ macro(DeclareTargets chunk_tag name)
|
||||
endmacro()
|
||||
|
||||
set(add_to_firmware TRUE)
|
||||
set(MODE_FLAGS "-O3")
|
||||
|
||||
|
||||
### ADS-B RX
|
||||
@@ -498,6 +500,7 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PWTH weather)
|
||||
|
||||
set(MODE_FLAGS "-Os")
|
||||
|
||||
### Flash Utility
|
||||
|
||||
@@ -559,7 +562,7 @@ DeclareTargets(PUSB sd_over_usb)
|
||||
|
||||
### Place external app and disabled images below so they don't get added to the firmware
|
||||
set(add_to_firmware FALSE)
|
||||
|
||||
set(MODE_FLAGS "-O3")
|
||||
|
||||
### ACARS RX
|
||||
|
||||
|
||||
@@ -38,28 +38,72 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
} else
|
||||
sample_count--;
|
||||
|
||||
if (tone_shape == 0) {
|
||||
if (modulation == 0) {
|
||||
// CW
|
||||
re = 127; // max. signed 8 bits value . (-128 ...+127), max. amplitude , static phasor at 0º
|
||||
im = 0;
|
||||
} else {
|
||||
if (tone_shape == 1) {
|
||||
} else if (modulation == 2) {
|
||||
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
|
||||
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
|
||||
im = 0;
|
||||
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
} else if (modulation == 3) {
|
||||
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment.
|
||||
switch (((tone_phase & 0xFF000000) >> 24)) {
|
||||
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
|
||||
/* "00" */
|
||||
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
|
||||
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
|
||||
break;
|
||||
|
||||
case 64 ... 127:
|
||||
/* "01" */
|
||||
re = (sine_table_i8[96]); // symbol-phasor 135º
|
||||
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
|
||||
break;
|
||||
|
||||
case 128 ... 191:
|
||||
/* "10" */
|
||||
re = (sine_table_i8[159]); // symbol-phasor 225º
|
||||
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
|
||||
break;
|
||||
|
||||
case 192 ... 255:
|
||||
/* "11" */
|
||||
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
|
||||
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
} else if (modulation == 7) {
|
||||
// Pulsed CW, 25% duty cycle.
|
||||
if (tone_phase < 1073741824) // 1073741824 = 2^32*(25/100)
|
||||
re = 127;
|
||||
else
|
||||
re = 0;
|
||||
im = 0;
|
||||
tone_phase += tone_delta; // In Pulsed CW we are using to calculate each periode.
|
||||
} else { // Other modulations: FM, DSB, AM
|
||||
if (tone_shape == 0) {
|
||||
// Sine
|
||||
sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]);
|
||||
} else if (tone_shape == 2) {
|
||||
} else if (tone_shape == 1) {
|
||||
// Triangle
|
||||
int8_t a = (tone_phase & 0xFF000000) >> 24;
|
||||
sample = (a & 0x80) ? ((a << 1) ^ 0xFF) - 0x80 : (a << 1) + 0x80;
|
||||
} else if (tone_shape == 3) {
|
||||
} else if (tone_shape == 2) {
|
||||
// Saw up
|
||||
sample = ((tone_phase & 0xFF000000) >> 24);
|
||||
} else if (tone_shape == 4) {
|
||||
} else if (tone_shape == 3) {
|
||||
// Saw down
|
||||
sample = ((tone_phase & 0xFF000000) >> 24) ^ 0xFF;
|
||||
} else if (tone_shape == 5) {
|
||||
} else if (tone_shape == 4) {
|
||||
// Square
|
||||
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
|
||||
} else if (tone_shape == 6) {
|
||||
} else if (tone_shape == 5) {
|
||||
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
|
||||
@@ -79,49 +123,13 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (counter == 15) {
|
||||
counter = 0;
|
||||
}
|
||||
} else if (tone_shape == 7) {
|
||||
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
|
||||
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
|
||||
im = 0;
|
||||
} else if (tone_shape == 8) {
|
||||
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment .
|
||||
|
||||
switch (((tone_phase & 0xFF000000) >> 24)) {
|
||||
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
|
||||
/* "00" */
|
||||
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
|
||||
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
|
||||
break;
|
||||
|
||||
case 64 ... 127:
|
||||
/* "01" */
|
||||
re = (sine_table_i8[96]); // symbol-phasor 135º
|
||||
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
|
||||
break;
|
||||
break;
|
||||
|
||||
case 128 ... 191:
|
||||
/* "10" */
|
||||
re = (sine_table_i8[159]); // symbol-phasor 225º
|
||||
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
|
||||
break;
|
||||
|
||||
case 192 ... 255:
|
||||
/* "11" */
|
||||
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
|
||||
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tone_shape != 6) { //(all except Pseudo Random White Noise). We are in (1):periodic signals or (2):BPSK/QPSK , in both cases ,we need Tone updated acum sum phases to modulate in FM / or control phasor phase (BPSK & QPSK.)
|
||||
tone_phase += tone_delta; // In periodic signals(Sine/triangle/square) we are using to FM mod. in BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
if (tone_shape != 5) { // All periodic except Pseudo Random White Noise.
|
||||
tone_phase += tone_delta; // In periodic signals we are using phase to generate the tone to be modulated.
|
||||
}
|
||||
|
||||
if (tone_shape < 7) { // All Option shape signals except BPSK(7) & QPSK(8) we are modulating in FM. (Those two has phase shift modulation XPSK , not FM )
|
||||
if (modulation == 1) {
|
||||
// Do FM modulation
|
||||
delta = sample * fm_delta;
|
||||
|
||||
@@ -130,6 +138,21 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
re = (sine_table_i8[(sphase & 0xFF000000) >> 24]); // sin LUT is not dealing with decimals , output range [-128 ,...127]
|
||||
im = (sine_table_i8[(phase & 0xFF000000) >> 24]);
|
||||
|
||||
} else if (modulation == 4) {
|
||||
// Do Double Side Band modulation
|
||||
re = sample;
|
||||
im = 0;
|
||||
|
||||
} else if (modulation == 5) {
|
||||
// Do AM modulation (100% mod index)
|
||||
re = 64 + (sample >> 1); // 64 = 127 - (127 >> 1): carrier level without modulating signal
|
||||
im = 0;
|
||||
|
||||
} else if (modulation == 6) {
|
||||
// Do AM modulation (50% mod index)
|
||||
re = 96 + (sample >> 2); // 96 = 127 - (127 >> 2): carrier level without modulating signal
|
||||
im = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +177,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
|
||||
auto_off = false;
|
||||
|
||||
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
|
||||
tone_shape = message.shape;
|
||||
tone_shape = message.shape & 0xF;
|
||||
modulation = (message.shape & 0xF0) >> 4;
|
||||
|
||||
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
|
||||
lfsr_16 = seed_value_16; // init lfsr 16 bits.
|
||||
|
||||
@@ -36,7 +36,7 @@ class SigGenProcessor : public BasebandProcessor {
|
||||
bool configured{false};
|
||||
|
||||
uint32_t tone_delta{0}, fm_delta{}, tone_phase{0};
|
||||
uint8_t tone_shape{};
|
||||
uint8_t tone_shape{}, modulation{};
|
||||
uint32_t sample_count{0};
|
||||
bool auto_off{};
|
||||
int32_t phase{0}, sphase{0}, delta{0}; // they may have sign in the pseudo random sample generation.
|
||||
|
||||
@@ -1060,6 +1060,7 @@ DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluste
|
||||
}
|
||||
}
|
||||
/* go to default */
|
||||
__attribute__ ((fallthrough));
|
||||
#endif
|
||||
default:
|
||||
val = 1; /* Internal error */
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
*/
|
||||
|
||||
#define _USE_LFN 2
|
||||
#define _USE_LFN 3
|
||||
#define _MAX_LFN 255
|
||||
/* The _USE_LFN switches the support of long file name (LFN).
|
||||
/
|
||||
@@ -189,7 +189,7 @@
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the file system object (FATFS) is used for the file data transfer. */
|
||||
|
||||
#define _FS_EXFAT 0
|
||||
#define _FS_EXFAT 1
|
||||
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
|
||||
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
@@ -231,6 +231,7 @@ void lcd_start_ram_write(
|
||||
lcd_caset(p.x(), p.x() + s.width() - 1);
|
||||
lcd_paset(p.y(), p.y() + s.height() - 1);
|
||||
lcd_ramwr_start();
|
||||
io.update_cached_values();
|
||||
}
|
||||
|
||||
void lcd_start_ram_read(
|
||||
|
||||
@@ -77,10 +77,24 @@ void IO::reference_oscillator(const bool enable) {
|
||||
io_write(1, io_reg);
|
||||
}
|
||||
|
||||
bool IO::get_dark_cover() {
|
||||
return portapack::persistent_memory::apply_fake_brightness() & (!portapack::persistent_memory::config_lcd_inverted_mode());
|
||||
}
|
||||
|
||||
bool IO::get_is_inverted() {
|
||||
return portapack::persistent_memory::config_lcd_inverted_mode();
|
||||
}
|
||||
|
||||
uint8_t IO::get_brightness() {
|
||||
return portapack::persistent_memory::fake_brightness_level();
|
||||
}
|
||||
|
||||
void IO::update_cached_values() {
|
||||
inverted_enabled = get_is_inverted();
|
||||
dark_cover_enabled = get_dark_cover();
|
||||
brightness = get_brightness();
|
||||
}
|
||||
|
||||
uint32_t IO::io_update(const TouchPinsConfig write_value) {
|
||||
/* Very touchy code to save context of PortaPack data bus while the
|
||||
* resistive touch pin drive is changed. Order of operations is
|
||||
|
||||
@@ -162,6 +162,9 @@ class IO {
|
||||
}
|
||||
|
||||
void lcd_write_pixel(ui::Color pixel) {
|
||||
if (dark_cover_enabled) {
|
||||
pixel.v = DARKENED_PIXEL(pixel.v, brightness);
|
||||
}
|
||||
lcd_write_data(pixel.v);
|
||||
}
|
||||
|
||||
@@ -170,12 +173,18 @@ class IO {
|
||||
}
|
||||
|
||||
void lcd_write_pixels(ui::Color pixel, size_t n) {
|
||||
if (dark_cover_enabled) {
|
||||
pixel.v = DARKENED_PIXEL(pixel.v, brightness);
|
||||
}
|
||||
while (n--) {
|
||||
lcd_write_data(pixel.v);
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_write_pixels_unrolled8(ui::Color pixel, size_t n) {
|
||||
if (dark_cover_enabled) {
|
||||
pixel.v = DARKENED_PIXEL(pixel.v, brightness);
|
||||
}
|
||||
auto v = pixel.v;
|
||||
n >>= 3;
|
||||
while (n--) {
|
||||
@@ -222,7 +231,13 @@ class IO {
|
||||
|
||||
return switches_raw;
|
||||
}
|
||||
bool inverted_enabled = false;
|
||||
bool dark_cover_enabled = false;
|
||||
uint8_t brightness = 0;
|
||||
bool get_is_inverted();
|
||||
bool get_dark_cover();
|
||||
uint8_t get_brightness();
|
||||
void update_cached_values();
|
||||
|
||||
uint32_t io_update(const TouchPinsConfig write_value);
|
||||
|
||||
@@ -404,6 +419,13 @@ class IO {
|
||||
const auto value_low = data_read();
|
||||
uint32_t original_value = (value_high << 8) | value_low;
|
||||
|
||||
if (inverted_enabled) return original_value;
|
||||
|
||||
if (dark_cover_enabled) {
|
||||
// this is read data, so if the fake brightness is enabled AKA get_dark_cover() == true,
|
||||
// then shift to back side AKA UNDARKENED_PIXEL, to prevent read shifted darkern info
|
||||
original_value = UNDARKENED_PIXEL(original_value, brightness);
|
||||
}
|
||||
return original_value;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ struct ui_config_t {
|
||||
bool hide_clock : 1;
|
||||
bool clock_show_date : 1;
|
||||
bool clkout_enabled : 1;
|
||||
bool unused_FB : 1; // apply_fake_brightness
|
||||
bool apply_fake_brightness : 1; // Fake brightness level, which eventually could be something along the lines of apply_pwm_brightness
|
||||
bool stealth_mode : 1;
|
||||
bool config_login : 1;
|
||||
bool config_splash : 1;
|
||||
@@ -130,7 +130,7 @@ struct ui_config2_t {
|
||||
bool hide_sd_card : 1;
|
||||
|
||||
bool hide_mute : 1;
|
||||
bool UNUSED_3 : 1;
|
||||
bool hide_fake_brightness : 1;
|
||||
bool hide_numeric_battery : 1;
|
||||
bool hide_battery_icon : 1;
|
||||
bool override_batt_calc : 1;
|
||||
@@ -212,7 +212,7 @@ struct data_t {
|
||||
bool updown_frequency_rx_correction;
|
||||
bool updown_frequency_tx_correction;
|
||||
bool lcd_inverted_mode : 1;
|
||||
bool UNUSED_5 : 1;
|
||||
bool encoder_dial_direction : 1; // false = normal, true = reverse
|
||||
bool UNUSED_6 : 1;
|
||||
bool UNUSED_7 : 1;
|
||||
|
||||
@@ -226,6 +226,9 @@ struct data_t {
|
||||
// Rotary encoder dial sensitivity (encoder.cpp/hpp)
|
||||
uint16_t encoder_dial_sensitivity : 4;
|
||||
|
||||
// fake brightness level (not switch, switch is in another place)
|
||||
uint16_t fake_brightness_level : 4;
|
||||
|
||||
// Encoder rotation rate multiplier for larger increments when rotated rapidly
|
||||
uint16_t encoder_rate_multiplier : 4;
|
||||
|
||||
@@ -287,7 +290,7 @@ struct data_t {
|
||||
updown_frequency_rx_correction(false),
|
||||
updown_frequency_tx_correction(false),
|
||||
lcd_inverted_mode(false),
|
||||
UNUSED_5(false),
|
||||
encoder_dial_direction(false),
|
||||
UNUSED_6(false),
|
||||
UNUSED_7(false),
|
||||
|
||||
@@ -297,6 +300,7 @@ struct data_t {
|
||||
frequency_tx_correction(0),
|
||||
|
||||
encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL),
|
||||
fake_brightness_level(BRIGHTNESS_50),
|
||||
encoder_rate_multiplier(1),
|
||||
UNUSED(0),
|
||||
|
||||
@@ -413,7 +417,8 @@ void defaults() {
|
||||
set_config_splash(true);
|
||||
set_config_disable_external_tcxo(false);
|
||||
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
|
||||
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
|
||||
set_encoder_dial_direction(false); // false = normal, true = reverse
|
||||
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
|
||||
set_menu_color(Color::grey());
|
||||
set_ui_hide_numeric_battery(true); // hide the numeric battery by default - no space to display it
|
||||
|
||||
@@ -465,6 +470,7 @@ void init() {
|
||||
set_config_mode_storage_direct(config_mode_backup);
|
||||
|
||||
// Firmware upgrade handling - adjust newly defined fields where 0 is an invalid default
|
||||
if (fake_brightness_level() == 0) set_fake_brightness_level(BRIGHTNESS_50);
|
||||
if (menu_color().v == 0) set_menu_color(Color::grey());
|
||||
}
|
||||
|
||||
@@ -645,6 +651,10 @@ bool stealth_mode() {
|
||||
return data->ui_config.stealth_mode;
|
||||
}
|
||||
|
||||
bool apply_fake_brightness() {
|
||||
return data->ui_config.apply_fake_brightness;
|
||||
}
|
||||
|
||||
bool config_login() {
|
||||
return data->ui_config.config_login;
|
||||
}
|
||||
@@ -748,6 +758,10 @@ void set_config_backlight_timer(const backlight_config_t& new_value) {
|
||||
data->ui_config.enable_backlight_timeout = static_cast<uint8_t>(new_value.timeout_enabled());
|
||||
}
|
||||
|
||||
void set_apply_fake_brightness(const bool v) {
|
||||
data->ui_config.apply_fake_brightness = v;
|
||||
}
|
||||
|
||||
uint32_t pocsag_last_address() {
|
||||
return data->pocsag_last_address;
|
||||
}
|
||||
@@ -934,7 +948,9 @@ bool ui_hide_clock() {
|
||||
bool ui_hide_sd_card() {
|
||||
return data->ui_config2.hide_sd_card;
|
||||
}
|
||||
|
||||
bool ui_hide_fake_brightness() {
|
||||
return data->ui_config2.hide_fake_brightness;
|
||||
}
|
||||
bool ui_hide_numeric_battery() {
|
||||
return data->ui_config2.hide_numeric_battery;
|
||||
}
|
||||
@@ -977,6 +993,9 @@ void set_ui_hide_clock(bool v) {
|
||||
void set_ui_hide_sd_card(bool v) {
|
||||
data->ui_config2.hide_sd_card = v;
|
||||
}
|
||||
void set_ui_hide_fake_brightness(bool v) {
|
||||
data->ui_config2.hide_fake_brightness = v;
|
||||
}
|
||||
void set_ui_hide_numeric_battery(bool v) {
|
||||
data->ui_config2.hide_numeric_battery = v;
|
||||
}
|
||||
@@ -1062,6 +1081,13 @@ void set_encoder_rate_multiplier(uint8_t v) {
|
||||
data->encoder_rate_multiplier = v;
|
||||
}
|
||||
|
||||
bool encoder_dial_direction() {
|
||||
return data->encoder_dial_direction;
|
||||
}
|
||||
void set_encoder_dial_direction(bool v) {
|
||||
data->encoder_dial_direction = v;
|
||||
}
|
||||
|
||||
// Recovery mode magic value storage
|
||||
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
|
||||
|
||||
@@ -1094,6 +1120,26 @@ void set_config_dst(dst_config_t v) {
|
||||
rtc_time::dst_init();
|
||||
}
|
||||
|
||||
// Fake brightness level (switch is in another place)
|
||||
uint8_t fake_brightness_level() {
|
||||
return data->fake_brightness_level;
|
||||
}
|
||||
void set_fake_brightness_level(uint8_t v) {
|
||||
data->fake_brightness_level = v;
|
||||
}
|
||||
|
||||
// Cycle through 4 brightness options: disabled -> enabled/50% -> enabled/25% -> enabled/12.5% -> disabled
|
||||
void toggle_fake_brightness_level() {
|
||||
bool fbe = apply_fake_brightness();
|
||||
if (config_lcd_inverted_mode()) return; // for now only inverted mode OR fake brightness
|
||||
if ((!fbe) || (data->fake_brightness_level >= BRIGHTNESS_12p5)) {
|
||||
set_apply_fake_brightness(!fbe);
|
||||
data->fake_brightness_level = BRIGHTNESS_50;
|
||||
} else {
|
||||
data->fake_brightness_level++;
|
||||
}
|
||||
}
|
||||
|
||||
// Menu Color Scheme
|
||||
Color menu_color() {
|
||||
return data->menu_color;
|
||||
@@ -1208,9 +1254,11 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(data->frequency_tx_correction));
|
||||
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
|
||||
pmem_dump_file.write_line("encoder_rate_multiplier: " + to_string_dec_uint(data->encoder_rate_multiplier));
|
||||
pmem_dump_file.write_line("encoder_dial_direction: " + to_string_dec_uint(data->encoder_dial_direction)); // 0 = normal, 1 = reverse
|
||||
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
|
||||
pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8));
|
||||
pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)data->dst_config.v, 8));
|
||||
pmem_dump_file.write_line("fake_brightness_level: " + to_string_dec_uint(data->fake_brightness_level));
|
||||
pmem_dump_file.write_line("menu_color: 0x" + to_string_hex(data->menu_color.v, 4));
|
||||
pmem_dump_file.write_line("touchscreen_threshold: " + to_string_dec_uint(data->touchscreen_threshold));
|
||||
|
||||
@@ -1227,6 +1275,7 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("ui_config hide_clock: " + to_string_dec_uint(data->ui_config.hide_clock));
|
||||
pmem_dump_file.write_line("ui_config clock_with_date: " + to_string_dec_uint(data->ui_config.clock_show_date));
|
||||
pmem_dump_file.write_line("ui_config clkout_enabled: " + to_string_dec_uint(data->ui_config.clkout_enabled));
|
||||
pmem_dump_file.write_line("ui_config apply_fake_brightness: " + to_string_dec_uint(data->ui_config.apply_fake_brightness));
|
||||
pmem_dump_file.write_line("ui_config stealth_mode: " + to_string_dec_uint(data->ui_config.stealth_mode));
|
||||
pmem_dump_file.write_line("ui_config config_login: " + to_string_dec_uint(data->ui_config.config_login));
|
||||
pmem_dump_file.write_line("ui_config config_splash: " + to_string_dec_uint(data->ui_config.config_splash));
|
||||
@@ -1241,6 +1290,7 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("ui_config2 hide_clock: " + to_string_dec_uint(data->ui_config2.hide_clock));
|
||||
pmem_dump_file.write_line("ui_config2 hide_sd_card: " + to_string_dec_uint(data->ui_config2.hide_sd_card));
|
||||
pmem_dump_file.write_line("ui_config2 hide_mute: " + to_string_dec_uint(data->ui_config2.hide_mute));
|
||||
pmem_dump_file.write_line("ui_config2 hide_fake_brightness: " + to_string_dec_uint(data->ui_config2.hide_fake_brightness));
|
||||
pmem_dump_file.write_line("ui_config2 hide_battery_icon: " + to_string_dec_uint(data->ui_config2.hide_battery_icon));
|
||||
pmem_dump_file.write_line("ui_config2 hide_numeric_battery: " + to_string_dec_uint(data->ui_config2.hide_numeric_battery));
|
||||
pmem_dump_file.write_line("ui_config2 theme_id: " + to_string_dec_uint(data->ui_config2.theme_id));
|
||||
|
||||
@@ -135,6 +135,12 @@ typedef union {
|
||||
} dst_config_t;
|
||||
static_assert(sizeof(dst_config_t) == sizeof(uint32_t));
|
||||
|
||||
enum fake_brightness_level_options {
|
||||
BRIGHTNESS_50 = 1,
|
||||
BRIGHTNESS_25 = 2,
|
||||
BRIGHTNESS_12p5 = 3, // 12p5 is 12.5
|
||||
};
|
||||
|
||||
namespace cache {
|
||||
|
||||
/* Set values in cache to sensible defaults. */
|
||||
@@ -246,6 +252,8 @@ uint8_t encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
uint8_t encoder_rate_multiplier();
|
||||
void set_encoder_rate_multiplier(uint8_t v);
|
||||
bool encoder_dial_direction();
|
||||
void set_encoder_dial_direction(bool v);
|
||||
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
@@ -267,6 +275,15 @@ uint16_t clkout_freq();
|
||||
dst_config_t config_dst();
|
||||
void set_config_dst(dst_config_t v);
|
||||
|
||||
/* Fake brightness */
|
||||
// switch (if do color change):
|
||||
bool apply_fake_brightness();
|
||||
void set_apply_fake_brightness(const bool v);
|
||||
// level (color change level):
|
||||
uint8_t fake_brightness_level();
|
||||
void set_fake_brightness_level(uint8_t v);
|
||||
void toggle_fake_brightness_level();
|
||||
|
||||
/* Touchscreen threshold */
|
||||
uint16_t touchscreen_threshold();
|
||||
void set_touchscreen_threshold(uint16_t v);
|
||||
@@ -319,6 +336,7 @@ bool ui_hide_camera();
|
||||
bool ui_hide_sleep();
|
||||
bool ui_hide_bias_tee();
|
||||
bool ui_hide_clock();
|
||||
bool ui_hide_fake_brightness();
|
||||
bool ui_hide_numeric_battery();
|
||||
bool ui_hide_battery_icon();
|
||||
bool ui_hide_sd_card();
|
||||
@@ -332,6 +350,7 @@ void set_ui_hide_camera(bool v);
|
||||
void set_ui_hide_sleep(bool v);
|
||||
void set_ui_hide_bias_tee(bool v);
|
||||
void set_ui_hide_clock(bool v);
|
||||
void set_ui_hide_fake_brightness(bool v);
|
||||
void set_ui_hide_numeric_battery(bool v);
|
||||
void set_ui_hide_battery_icon(bool v);
|
||||
void set_ui_hide_sd_card(bool v);
|
||||
|
||||
@@ -35,6 +35,13 @@ struct JammerChannel {
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
struct HopperChannel {
|
||||
bool enabled;
|
||||
uint64_t center;
|
||||
uint32_t width;
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
struct ToneDef {
|
||||
uint32_t delta;
|
||||
uint32_t duration;
|
||||
@@ -61,7 +68,10 @@ struct SharedMemory {
|
||||
|
||||
union {
|
||||
ToneData tones_data;
|
||||
JammerChannel jammer_channels[24];
|
||||
struct {
|
||||
JammerChannel jammer_channels[24];
|
||||
HopperChannel hopper_channels[24];
|
||||
} dummy_seperate;
|
||||
uint8_t data[512];
|
||||
} bb_data{{{{0, 0}}, 0, {0}}};
|
||||
|
||||
|
||||
@@ -170,6 +170,81 @@ temp_humid Packet::get_temp_humid() const {
|
||||
result.humid = 0;
|
||||
result.temp = 0;
|
||||
|
||||
if (type_ == Type::Meteomodem_M10) {
|
||||
// https://github.com/projecthorus/radiosonde_auto_rx/blob/master/demod/mod/m10mod.c
|
||||
// temp:
|
||||
uint16_t ADC_Ti_raw = (reader_bi_m.read(0x49 * 8, 8) << 8) | reader_bi_m.read(0x48 * 8, 8) << 8; // int.temp.diode, ref: 4095->1.5V
|
||||
if (ADC_Ti_raw != 0) {
|
||||
// internal
|
||||
float vti, ti;
|
||||
// INCH1A (temp.diode), slau144
|
||||
vti = ADC_Ti_raw / 4095.0 * 1.5; // V_REF+ = 1.5V, no calibration
|
||||
ti = (vti - 0.986) / 0.00355; // 0.986/0.00355=277.75, 1.5/4095/0.00355=0.1032
|
||||
result.temp = ti;
|
||||
}
|
||||
// NTC - Thermistor Shibaura PB5 - 41E
|
||||
float p0 = 1.07303516e-03,
|
||||
p1 = 2.41296733e-04,
|
||||
p2 = 2.26744154e-06,
|
||||
p3 = 6.52855181e-08;
|
||||
// T/K = 1/( p0 + p1*ln(R) + p2*ln(R)^2 + p3*ln(R)^3 )
|
||||
|
||||
// range/scale 0, 1, 2: // M10-pcb
|
||||
float Rs_T[3] = {12.1e3, 36.5e3, 475.0e3}; // bias/series
|
||||
float Rp[3] = {1e20, 330.0e3, 2000.0e3}; // parallel, Rp[0]=inf
|
||||
|
||||
uint8_t scT; // {0,1,2}, range/scale voltage divider
|
||||
uint16_t ADC_RT; // ADC12 P6.7(A7) , adr_0377h,adr_0376h
|
||||
// uint16_t Tcal[2]; // adr_1000h[scT*4]
|
||||
|
||||
float adc_max = 4095.0; // ADC12
|
||||
float x, R;
|
||||
float T = 0; // T/Kelvin
|
||||
|
||||
scT = reader_bi_m.read(0x3E * 8, 8); // adr_0455h
|
||||
ADC_RT = (reader_bi_m.read(0x40 * 8, 8) << 8) | reader_bi_m.read(0x3F * 8, 8);
|
||||
if (ADC_RT != 0) {
|
||||
ADC_RT -= 0xA000;
|
||||
// Tcal[0] = (reader_bi_m.read(0x42 * 8, 8) << 8) | reader_bi_m.read(0x41 * 8, 8);
|
||||
// Tcal[1] = (reader_bi_m.read(0x44 * 8, 8) << 8) | reader_bi_m.read(0x43 * 8, 8);
|
||||
|
||||
x = (adc_max - ADC_RT) / ADC_RT; // (Vcc-Vout)/Vout
|
||||
if (scT < 3)
|
||||
R = Rs_T[scT] / (x - Rs_T[scT] / Rp[scT]);
|
||||
else
|
||||
R = -1;
|
||||
|
||||
if (R > 0) T = 1 / (p0 + p1 * log(R) + p2 * log(R) * log(R) + p3 * log(R) * log(R) * log(R));
|
||||
result.temp = T - 273.15;
|
||||
}
|
||||
|
||||
// humidity:
|
||||
// get count rh:
|
||||
float TBCCR1 = (reader_bi_m.read(0x35 * 8, 8) | (reader_bi_m.read(0x36 * 8, 8) << 8) | (reader_bi_m.read(0x37 * 8, 8) << 16)) / 1000.0;
|
||||
// get count 55:
|
||||
float TBCREF = (reader_bi_m.read(0x32 * 8, 8) | (reader_bi_m.read(0x33 * 8, 8) << 8) | (reader_bi_m.read(0x34 * 8, 8) << 16)) / 1000.0;
|
||||
if (TBCREF != 0) {
|
||||
float cRHc55 = TBCCR1 / TBCREF; //;get_count_RH(gpx) / get_count_55(gpx); // CalRef 55%RH , T=20C ?
|
||||
// get_Tntc2: --unused.
|
||||
// float Rs = 22.1e3; // P5.6=Vcc
|
||||
// float R25 = 2.2e3;
|
||||
// float b = 3650.0; // B/Kelvin
|
||||
// float T25 = 25.0 + 273.15; // T0=25C, R0=R25=5k
|
||||
// -> Steinhart-Hart coefficients (polyfit):
|
||||
|
||||
// cRHc55_RH:
|
||||
// float TH = get_Tntc2(gpx); --unused
|
||||
float rh = (cRHc55 - 0.8955) / 0.002; // UPSI linear transfer function
|
||||
// temperature compensation
|
||||
float T0 = 0.0, T1 = -30.0; // T/C
|
||||
if (result.temp < T0) rh += T0 - result.temp / 5.5; // approx/empirical
|
||||
if (result.temp < T1) rh *= 1.0 + (T1 - result.temp) / 75.0; // approx/empirical
|
||||
if (rh < 0.0) rh = 0.0;
|
||||
if (rh > 100.0) rh = 100.0;
|
||||
result.humid = rh;
|
||||
}
|
||||
}
|
||||
|
||||
if (type_ == Type::Vaisala_RS41_SG && crc_ok_RS41()) // Only process if packet is healthy
|
||||
{
|
||||
// memset(calfrchk, 0, 51); // is this necessary ? only if the sondeID changes (new sonde)
|
||||
|
||||
@@ -71,7 +71,8 @@ enum app_location_t : uint32_t {
|
||||
RX,
|
||||
TX,
|
||||
DEBUG,
|
||||
HOME
|
||||
HOME,
|
||||
SETTINGS
|
||||
};
|
||||
|
||||
struct standalone_application_information_t {
|
||||
|
||||
@@ -2606,15 +2606,19 @@ Waveform::Waveform(
|
||||
uint32_t length,
|
||||
uint32_t offset,
|
||||
bool digital,
|
||||
Color color)
|
||||
Color color,
|
||||
bool clickable)
|
||||
: Widget{parent_rect},
|
||||
data_{data},
|
||||
length_{length},
|
||||
offset_{offset},
|
||||
digital_{digital},
|
||||
color_{color} {
|
||||
// set_focusable(false);
|
||||
// previous_data.resize(length_, 0);
|
||||
color_{color},
|
||||
clickable_{clickable} {
|
||||
if (clickable) {
|
||||
set_focusable(true);
|
||||
// previous_data.resize(length_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Waveform::set_cursor(const uint32_t i, const int16_t position) {
|
||||
@@ -2641,9 +2645,107 @@ void Waveform::set_length(const uint32_t new_length) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Waveform::is_paused() const {
|
||||
return paused_;
|
||||
}
|
||||
|
||||
void Waveform::set_paused(bool paused) {
|
||||
paused_ = paused;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool Waveform::is_clickable() const {
|
||||
return clickable_;
|
||||
}
|
||||
|
||||
void Waveform::getAccessibilityText(std::string& result) {
|
||||
// no idea what this is in use in any places, but others have it
|
||||
result = paused_ ? "paused waveform" : "waveform";
|
||||
}
|
||||
|
||||
void Waveform::getWidgetName(std::string& result) {
|
||||
result = "Waveform";
|
||||
}
|
||||
|
||||
bool Waveform::on_key(const KeyEvent key) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Waveform::on_keyboard(const KeyboardEvent key) {
|
||||
// no idea what this is for, but others have it
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == 32 || key == 10) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Waveform::on_touch(const TouchEvent event) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
switch (event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
focus();
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Waveform::paint(Painter& painter) {
|
||||
// previously it's upside down , low level is up and high level is down, which doesn't make sense,
|
||||
// if that was made for a reason, feel free to revert.
|
||||
|
||||
if (paused_) {
|
||||
// TODO: this is bad: that it still enter this func and still consume resources.
|
||||
// even do a if(paused_) return; comsume too, but not that much.
|
||||
|
||||
// if (dirty()) {
|
||||
// clear
|
||||
// painter.fill_rectangle_unrolled8(screen_rect(), Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
// // draw "PAUSED" text
|
||||
// const auto r = screen_rect();
|
||||
// painter.draw_string(
|
||||
// {r.center().x() - 24, r.center().y() - 8},
|
||||
// style(),
|
||||
// "PAUSED");
|
||||
|
||||
// if (show_cursors) {
|
||||
// for (uint32_t n = 0; n < 2; n++) {
|
||||
// painter.draw_vline(
|
||||
// Point(std::min(screen_rect().size().width(), (int)cursors[n]), screen_rect().location().y()),
|
||||
// screen_rect().size().height(),
|
||||
// cursor_colors[n]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
// not paused
|
||||
size_t n;
|
||||
Coord y, y_offset = screen_rect().location().y();
|
||||
Coord prev_x = screen_rect().location().x(), prev_y;
|
||||
@@ -2701,6 +2803,13 @@ void Waveform::paint(Painter& painter) {
|
||||
cursor_colors[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// focused highlight border
|
||||
if (clickable_ && has_focus()) {
|
||||
painter.draw_rectangle(
|
||||
screen_rect(),
|
||||
Theme::getInstance()->fg_light->foreground);
|
||||
}
|
||||
}
|
||||
|
||||
/* VuMeter **************************************************************/
|
||||
|
||||
@@ -974,7 +974,9 @@ class SymField : public Widget {
|
||||
|
||||
class Waveform : public Widget {
|
||||
public:
|
||||
Waveform(Rect parent_rect, int16_t* data, uint32_t length, uint32_t offset, bool digital, Color color);
|
||||
std::function<void(Waveform&)> on_select{};
|
||||
|
||||
Waveform(Rect parent_rect, int16_t* data, uint32_t length, uint32_t offset, bool digital, Color color, bool clickable = false);
|
||||
|
||||
Waveform(const Waveform&) = delete;
|
||||
Waveform(Waveform&&) = delete;
|
||||
@@ -985,7 +987,17 @@ class Waveform : public Widget {
|
||||
void set_length(const uint32_t new_length);
|
||||
void set_cursor(const uint32_t i, const int16_t position);
|
||||
|
||||
bool is_paused() const;
|
||||
void set_paused(bool paused);
|
||||
bool is_clickable() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
|
||||
private:
|
||||
const Color cursor_colors[2] = {Theme::getInstance()->fg_cyan->foreground, Theme::getInstance()->fg_magenta->foreground};
|
||||
@@ -997,6 +1009,8 @@ class Waveform : public Widget {
|
||||
Color color_;
|
||||
int16_t cursors[2]{};
|
||||
bool show_cursors{false};
|
||||
bool paused_{false};
|
||||
bool clickable_{false};
|
||||
};
|
||||
|
||||
class VuMeter : public Widget {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 886 B |
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(standalone_apps)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
for fname in $(ls ../graphics/); do
|
||||
fname=$(echo "$fname" | awk -F'.' '{print $1}')
|
||||
out=$(grep -r --exclude="bitmap.hpp" "$fname" ../* )
|
||||
ret=$?
|
||||
if [[ "$ret" -ne 0 ]]; then
|
||||
echo "$fname not found"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 u-foka
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
set -e # exit immediatelly on any failure
|
||||
|
||||
build_make() {
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
# Copyleft Whiterose from the Dark Army
|
||||
#
|
||||
# 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.
|
||||
|
||||
# this tool allow you flash pp and copy the apps in fastest way. note that you need to have a sdcard switcher to switch sdcard to pc.
|
||||
# although, if you don't have one, you still can take out the sdcard and plug it into your pc, but i think it would not pretty fast.
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import glob
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
# config
|
||||
SUDO_PASSWORD = ""
|
||||
SDCARD_LABEL = "portapack"
|
||||
|
||||
|
||||
def write_command(ser, command, wait_response=None, remove_echo=False, dont_wait_ch=False):
|
||||
|
||||
# clean buffer
|
||||
while True:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
ser.read(ser.in_waiting)
|
||||
else:
|
||||
break
|
||||
except:
|
||||
break
|
||||
|
||||
command_with_newline = command + "\r\n"
|
||||
try:
|
||||
ser.write(command_with_newline.encode())
|
||||
except Exception as e:
|
||||
return f"write err: {str(e)}"
|
||||
|
||||
response = ""
|
||||
expected_echo = f"{command}\r\n"
|
||||
wait_response_met = wait_response is None
|
||||
|
||||
while True:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
msg = ser.read(ser.in_waiting).decode('utf-8')
|
||||
response += msg
|
||||
|
||||
if wait_response and wait_response in response:
|
||||
wait_response_met = True
|
||||
|
||||
if wait_response_met and (dont_wait_ch or "ch> " in response):
|
||||
if remove_echo:
|
||||
return response.replace(expected_echo, "")
|
||||
return response
|
||||
|
||||
except serial.SerialTimeoutException:
|
||||
continue
|
||||
except Exception as e:
|
||||
return f"e: {str(e)}"
|
||||
|
||||
def get_serial_devices():
|
||||
ports = serial.tools.list_ports.comports()
|
||||
|
||||
if not ports:
|
||||
print("no serial device found")
|
||||
return None
|
||||
|
||||
portapack_device = None
|
||||
for port in ports:
|
||||
if port.product == "PortaPack Mayhem":
|
||||
print(f"found your pp:")
|
||||
print(f"device: {port.device}")
|
||||
print(f"description: {port.description}")
|
||||
print(f"hwid: {port.hwid}")
|
||||
if port.manufacturer:
|
||||
print(f"manufacturer: {port.manufacturer}")
|
||||
if port.serial_number:
|
||||
print(f"serial_number: {port.serial_number}")
|
||||
portapack_device = port
|
||||
break
|
||||
|
||||
if not portapack_device:
|
||||
print("no pp device found")
|
||||
|
||||
return portapack_device
|
||||
|
||||
def run_sudo_command(cmd):
|
||||
|
||||
if not SUDO_PASSWORD:
|
||||
print("sudo password not set, please hard code it in the script")
|
||||
return
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
['sudo', '-S'] + cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True
|
||||
)
|
||||
stdout, stderr = process.communicate(input=SUDO_PASSWORD + '\n')
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, cmd, stdout, stderr)
|
||||
return stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"sudo command failed: {e}")
|
||||
raise
|
||||
|
||||
def get_pp_device_linux():
|
||||
try:
|
||||
cmd = ["lsblk", "-o", "NAME,LABEL,FSTYPE,SIZE,MOUNTPOINT", "-n", "-p"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# find your pp sd
|
||||
for line in result.stdout.splitlines():
|
||||
clean_line = line.replace('└─', '').replace('├─', '').strip()
|
||||
parts = clean_line.split()
|
||||
|
||||
if len(parts) >= 2 and SDCARD_LABEL in parts[1]: # checker
|
||||
device_path = parts[0]
|
||||
# if path valid
|
||||
if not os.path.exists(device_path):
|
||||
continue
|
||||
|
||||
# if mounted, return mountpoint
|
||||
if len(parts) >= 5:
|
||||
mountpoint = parts[4]
|
||||
return device_path, mountpoint
|
||||
|
||||
# if not, create temp dir and mount
|
||||
mount_point = tempfile.mkdtemp()
|
||||
print(f"mount {device_path} to {mount_point}")
|
||||
run_sudo_command(["mount", "-o", f"uid={os.getuid()},gid={os.getgid()}", device_path, mount_point])
|
||||
return device_path, mount_point
|
||||
|
||||
return None, None
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"command failed: {str(e)}")
|
||||
return None, None
|
||||
|
||||
def wait_for_pp_drive():
|
||||
while True:
|
||||
if os.name == 'nt': # win didn't test yet, TODO
|
||||
drives = [d + ':' for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if os.path.exists(d + ':')]
|
||||
for drive in drives:
|
||||
if os.path.basename(os.path.abspath(drive)).lower() == SDCARD_LABEL:
|
||||
return drive, None
|
||||
else: # linux and mac
|
||||
device_path, mount_point = get_pp_device_linux()
|
||||
if device_path and mount_point:
|
||||
return mount_point, device_path
|
||||
time.sleep(1)
|
||||
|
||||
def run():
|
||||
# worker 1: check device connected
|
||||
device = get_serial_devices()
|
||||
if not device:
|
||||
print("no pp device found")
|
||||
return
|
||||
|
||||
try:
|
||||
# worker 2: open serial and send hackrf cmd
|
||||
ser = serial.Serial(device.device, baudrate=115200, timeout=1)
|
||||
for i in range(2):
|
||||
write_command(ser, "hackrf")
|
||||
ser.close()
|
||||
|
||||
# worker 3: hint user to swtich sdcard to pc
|
||||
print("\033[92m\n\t+------------------------+")
|
||||
print("\t| switch PP sdcard to PC |")
|
||||
print("\t+------------------------+\033[0m")
|
||||
print("wait PP sdcard...")
|
||||
|
||||
# worker 4: wait pp sd
|
||||
mount_point, device_path = wait_for_pp_drive()
|
||||
if not mount_point:
|
||||
print("no pp sd found")
|
||||
return
|
||||
print(f"find pp sd: {mount_point}")
|
||||
|
||||
# worker 5: copy ppma and ppmp apps
|
||||
apps_dir = os.path.join(mount_point, 'APPS')
|
||||
if not os.path.exists(apps_dir):
|
||||
os.makedirs(apps_dir)
|
||||
|
||||
firmware_dir = os.path.join('.', 'firmware', 'application')
|
||||
for ext in ['ppma', 'ppmp']:
|
||||
for file in glob.glob(os.path.join(firmware_dir, f'*.{ext}')):
|
||||
dest = os.path.join(apps_dir, os.path.basename(file))
|
||||
print(f"cp: {os.path.basename(file)}")
|
||||
shutil.copy2(file, dest)
|
||||
|
||||
print("apps copy done")
|
||||
|
||||
# worker 6: umount pp sd
|
||||
if os.name != 'nt' and device_path:
|
||||
run_sudo_command(["umount", mount_point])
|
||||
os.rmdir(mount_point) # 删除临时挂载点
|
||||
|
||||
# worker 7: run flash cmd with hackrf_spiflash util
|
||||
print("\nflash...")
|
||||
process = subprocess.Popen(
|
||||
["hackrf_spiflash", "-w", "./firmware/portapack-mayhem-firmware.bin"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
while True:
|
||||
output = process.stdout.readline()
|
||||
if output:
|
||||
print(output.strip())
|
||||
if process.poll() is not None:
|
||||
break
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, "hackrf_spiflash")
|
||||
|
||||
# worker 8: hint user to swtich sdcard switcher back to pp
|
||||
print("\033[92m\n\t+------------------------------------------------+")
|
||||
print("\t| switch sdcard switcher back to pp, then reboot |")
|
||||
print("\t+------------------------------------------------+\033[0m")
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"serial comm error: {str(e)}")
|
||||
except OSError as e:
|
||||
print(f"file operation error: {str(e)}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"system command error: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"error: {str(e)}")
|
||||
# ensure umount when error
|
||||
if os.name != 'nt' and 'mount_point' in locals() and device_path:
|
||||
try:
|
||||
run_sudo_command(["umount", mount_point])
|
||||
os.rmdir(mount_point)
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
run()
|
||||
except Exception as e:
|
||||
print(f"error: {str(e)}")
|
||||
sys.exit(1)
|
||||
@@ -53,7 +53,7 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (auto& authors_line : authors_list) {
|
||||
for (auto& authors_line : mayhem_information_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
@@ -84,15 +84,13 @@ void AboutView::on_frame_sync() {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
menu_view.set_highlighted(3); // contributors block starting line
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
@@ -141,8 +139,12 @@ def get_contributors(url):
|
||||
|
||||
def generate_content(projects):
|
||||
project_contrib = []
|
||||
project_contrib.append("string_view authors_list[] = {\n")
|
||||
project_contrib.append(" \"# * List of contributors * \",\n")
|
||||
project_contrib.append("string_view mayhem_information_list[] = {\n")
|
||||
project_contrib.append(" \"#****** Mayhem Community ******\",\n")
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(" \" https://discord.mayhem.app\",\n")
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(" \"#**** List of contributors ****\",\n")
|
||||
for project in projects:
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(f" \"#{project[0]}:\",\n")
|
||||
@@ -170,7 +172,7 @@ def pp_create_ui_about_simple_cpp(cpp_file, cppheader, cppcontent, cppfooter):
|
||||
|
||||
def pp_change_ui_about_simple_cpp(cpp_file, cppcontent):
|
||||
content = []
|
||||
content_pattern = re.compile(r"string_view authors_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
content_pattern = re.compile(r"string_view mayhem_information_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
|
||||
# Read original file
|
||||
with open(cpp_file, 'r') as file:
|
||||
|
||||
@@ -161,3 +161,23 @@ write_image(spi_image, output_path)
|
||||
|
||||
percent_remaining = round(1000 * pad_size / spi_size) / 10;
|
||||
print("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)")
|
||||
|
||||
|
||||
# copy the fast flash script
|
||||
build_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'build')
|
||||
flash_py_path = os.path.join(build_dir, 'flash.py')
|
||||
|
||||
if not os.path.exists(flash_py_path):
|
||||
|
||||
current_dir = os.path.dirname(__file__)
|
||||
source_file = os.path.join(current_dir, 'fast_flash_pp_and_copy_apps.py')
|
||||
|
||||
if not os.path.exists(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
# cp
|
||||
import shutil
|
||||
print(f"\ncopy {source_file} to {flash_py_path}\n")
|
||||
shutil.copy2(source_file, flash_py_path)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#
|
||||
# copyleft Elliot Alderson from F society
|
||||
# copyleft Darlene Alderson from F society
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
import re
|
||||
import tkinter as tk
|
||||
from typing import Dict, List, Tuple
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
|
||||
"""
|
||||
TODO: Multile class in one file seperation
|
||||
TODO: Add more widget type
|
||||
|
||||
widget compatible guide:
|
||||
|
||||
1. add the preview color in the "widgets = {" table
|
||||
(note that this color is only for easier to distinguish)
|
||||
|
||||
2. add the widget type and also regex in the "parsers" table
|
||||
(note that your regex should apply all the possible constructor overload (re-impl))
|
||||
|
||||
"""
|
||||
|
||||
# pp hard coded vars
|
||||
screen_width = 240
|
||||
SCREEN_W = 240
|
||||
screen_height = 320
|
||||
CHARACTER_WIDTH = 8
|
||||
LINE_HEIGHT = 16
|
||||
topbar_offset = 16
|
||||
# widgets actually drew after the top bar,
|
||||
# for example if Y = 0 then Y on screen actually is 16
|
||||
|
||||
# scale factor
|
||||
scale = 2
|
||||
|
||||
widgets = {
|
||||
"Button": "lightgray",
|
||||
"NewButton": "lightyellow",
|
||||
"Text": "lightblue",
|
||||
"Rectangle": "lightgreen",
|
||||
"Image": "pink",
|
||||
"ImageButton": "lightpink",
|
||||
"NumberField": "peachpuff",
|
||||
"ProgressBar": "lightcoral",
|
||||
"Console": "wheat",
|
||||
"Checkbox": "plum",
|
||||
"Label": "lavender",
|
||||
"TextField": "paleturquoise",
|
||||
"OptionsField": "palegreen",
|
||||
"VuMeter": "sandybrown",
|
||||
"BigFrequency": "khaki"
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class Widget:
|
||||
name: str
|
||||
widget_type: str
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
text: str = ""
|
||||
|
||||
class WidgetParser:
|
||||
def __init__(self):
|
||||
|
||||
self.parsers: Dict[str, re.Pattern] = {
|
||||
'Button': re.compile( # TODO: fix those that using language helper
|
||||
r'Button\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'NewButton': re.compile(
|
||||
r'NewButton\s+(\w+)\s*\{\s*(?:\{([^}]+)\}|{}),\s*(?:"([^"]*)"|\{\}),\s*(?:[^,}]+(?:,\s*[^}]+)*|\{\})\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Text': re.compile(
|
||||
r'Text\s+(\w+)\s*\{\s*\{([^}]+)\}(?:\s*,\s*"([^"]*)")?\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'ProgressBar': re.compile(
|
||||
r'ProgressBar\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Console': re.compile(
|
||||
r'Console\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Label': re.compile(
|
||||
r'Label\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'VuMeter': re.compile(
|
||||
r'VuMeter\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+,\s*(?:true|false)\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'BigFrequency': re.compile(
|
||||
r'BigFrequency\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+\s*\};',
|
||||
re.MULTILINE
|
||||
)
|
||||
}
|
||||
|
||||
def parse_coordinates(self, coord_str: str) -> Tuple[int, int, int, int]:
|
||||
"""Parse coordinate string like '2 * 8, 8 * 16, 8 * 8, 3 * 16'"""
|
||||
if not coord_str or coord_str.isspace():
|
||||
return (0, 0, 0, 0) # for empty constructor fallback
|
||||
|
||||
# split and evaluate each coordinate expression
|
||||
coords = [eval(x.strip()) for x in coord_str.split(',')]
|
||||
|
||||
# ensure we have exactly 4 coordinates, pad with zeros if needed
|
||||
while len(coords) < 4:
|
||||
coords.append(0)
|
||||
|
||||
# only take the first 4 coordinates if there are more
|
||||
return tuple(coords[:4])
|
||||
|
||||
def parse_file(self, filepath: str) -> List[Widget]:
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
widgets = []
|
||||
for widget_type, pattern in self.parsers.items():
|
||||
matches = pattern.finditer(content)
|
||||
for match in matches:
|
||||
name = match.group(1) # Widget name is always in group 1
|
||||
coords = self.parse_coordinates(match.group(2) if match.group(2) else "")
|
||||
|
||||
# Only try to get text if the pattern has 3 or more groups
|
||||
text = ""
|
||||
try:
|
||||
if match.lastindex >= 3:
|
||||
text = match.group(3) if match.group(3) else ""
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
widgets.append(Widget(
|
||||
name=name,
|
||||
widget_type=widget_type,
|
||||
x=coords[0],
|
||||
y=coords[1],
|
||||
width=coords[2],
|
||||
height=coords[3],
|
||||
text=text
|
||||
))
|
||||
|
||||
return widgets
|
||||
|
||||
class WidgetPreview(tk.Tk):
|
||||
def __init__(self, widgets: List[Widget]):
|
||||
super().__init__()
|
||||
self.title("Widget Preview")
|
||||
|
||||
self.canvas = tk.Canvas(self, width=screen_width * scale, height=screen_height * scale, bg='white')
|
||||
self.canvas.pack(padx=10, pady=10)
|
||||
|
||||
self.all_text_elements = []
|
||||
self.draw_widgets(widgets)
|
||||
|
||||
def draw_widgets(self, widgets: List[Widget]):
|
||||
draw_top_bar(self)
|
||||
for widget in widgets:
|
||||
self.draw_widget(widget)
|
||||
|
||||
def draw_widget(self, widget: Widget):
|
||||
print(f"Drawing widget: {widget.name} ({widget.widget_type})")
|
||||
|
||||
x1 = widget.x * scale
|
||||
y1 = (widget.y + topbar_offset) * scale
|
||||
x2 = x1 + (widget.width * scale)
|
||||
y2 = y1 + (widget.height * scale)
|
||||
|
||||
print(f"Coordinates: ({x1}, {y1}), ({x2}, {y2})")
|
||||
|
||||
if widget.widget_type == "VuMeter":
|
||||
segment_height = widget.height / 8 * scale
|
||||
for i in range(8):
|
||||
self.canvas.create_rectangle(
|
||||
x1, y1 + (i * segment_height),
|
||||
x2, y1 + ((i+1) * segment_height),
|
||||
fill=widgets[widget.widget_type],
|
||||
outline="gray"
|
||||
)
|
||||
elif widget.widget_type == "BigFrequency":
|
||||
# Draw 7-segment style display
|
||||
self.canvas.create_rectangle(
|
||||
x1, y1, x2, y2,
|
||||
fill=widgets[widget.widget_type],
|
||||
outline="gray"
|
||||
)
|
||||
self.canvas.create_text(
|
||||
(x1 + x2) / 2,
|
||||
(y1 + y2) / 2,
|
||||
text="433.92" # placeholder text
|
||||
)
|
||||
|
||||
else:
|
||||
# defualt rendering
|
||||
rect_id = self.canvas.create_rectangle(
|
||||
x1, y1, x2, y2,
|
||||
fill=widgets[widget.widget_type]
|
||||
)
|
||||
|
||||
type_text_id = self.canvas.create_text(
|
||||
(x1 + x2) // 2,
|
||||
(y1 + y2) // 2,
|
||||
text=widget.widget_type
|
||||
)
|
||||
|
||||
detail_text_id = self.canvas.create_text(
|
||||
(x1 + x2) // 2,
|
||||
(y1 + y2) // 2,
|
||||
text=f"{widget.widget_type}|{widget.name}|{widget.text}",
|
||||
state='hidden'
|
||||
)
|
||||
|
||||
widget_texts = {
|
||||
'type': type_text_id,
|
||||
'detail': detail_text_id
|
||||
}
|
||||
self.all_text_elements.append(widget_texts)
|
||||
|
||||
# hover handlers
|
||||
def on_enter(event):
|
||||
for texts in self.all_text_elements:
|
||||
self.canvas.itemconfig(texts['type'], state='hidden')
|
||||
self.canvas.itemconfig(texts['detail'], state='hidden')
|
||||
self.canvas.itemconfig(detail_text_id, state='normal')
|
||||
self.canvas.tag_raise(detail_text_id)
|
||||
|
||||
def on_leave(event):
|
||||
for texts in self.all_text_elements:
|
||||
self.canvas.itemconfig(texts['type'], state='normal')
|
||||
self.canvas.tag_raise(texts['type'])
|
||||
self.canvas.itemconfig(texts['detail'], state='hidden')
|
||||
|
||||
for item_id in [rect_id, type_text_id, detail_text_id]:
|
||||
self.canvas.tag_bind(item_id, '<Enter>', on_enter)
|
||||
self.canvas.tag_bind(item_id, '<Leave>', on_leave)
|
||||
|
||||
def draw_top_bar(self):
|
||||
self.canvas.create_rectangle(0, 0, screen_width * scale, topbar_offset * scale, fill='lightblue')
|
||||
self.canvas.create_text(screen_width * scale // 2, topbar_offset * scale // 2, text='I\'m Top Bar, hover mouse on items to check details', fill='black')
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Preview UI widgets from hpp files')
|
||||
parser.add_argument('file', help='Path to the hpp file')
|
||||
args = parser.parse_args()
|
||||
|
||||
widget_parser = WidgetParser()
|
||||
widgets = widget_parser.parse_file(args.file)
|
||||
|
||||
app = WidgetPreview(widgets)
|
||||
app.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+1
-1
Submodule hackrf updated: 9bd91c6a85...65bf67f9b5
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user