mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 10:39:30 +00:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 910fd82051 | |||
| 866e12fbc5 | |||
| ccd71d9593 | |||
| e9b9ba4602 | |||
| 986e37a3c9 | |||
| 2ac36d2d0b | |||
| 3b79549129 | |||
| 160a778104 | |||
| e496f8ecc2 | |||
| 9d8132978f | |||
| 73c29f666f | |||
| d895c9fa91 | |||
| b5e66387c3 | |||
| 1fbfdbccf8 | |||
| 2648f18b12 | |||
| 908c0e2cd5 | |||
| 4f3530db5e | |||
| 73137891c4 | |||
| c28087ac44 | |||
| 9e9dd3a521 | |||
| 3bf689f339 | |||
| 86c326ef12 | |||
| 5ae97d47f1 | |||
| 762512b7dc | |||
| 9c848a6215 | |||
| 9c86e15ddb | |||
| 57ccdb0044 | |||
| 1172175cc8 | |||
| 9ed8ad5903 | |||
| e4da8b979e | |||
| 4b83fd8a40 | |||
| cd80da58d8 | |||
| a1f7010e08 | |||
| 3499d2fa75 | |||
| 948e039574 | |||
| 27dc37713b | |||
| ed834e3553 | |||
| ce08d93ae2 | |||
| 13fd1b1f3b | |||
| d04c781ada | |||
| 24605777a6 | |||
| b5ac792853 | |||
| 44c319dcc6 | |||
| a442971b81 | |||
| 8b5adb6bc1 | |||
| c3add0ce84 | |||
| 8725b01995 | |||
| 936e8279f9 | |||
| 6193023c8f | |||
| 1800939833 | |||
| 2f48fc2ef9 | |||
| 918ec0574f | |||
| 1139b22141 | |||
| 7598c25fc8 | |||
| 0f6a9277a8 | |||
| 6a8650d484 | |||
| 46d9e02684 | |||
| 367479d163 | |||
| bbbf5a37c8 | |||
| b2ad1fa979 | |||
| 998be5ba09 | |||
| 9e8d279298 | |||
| fd03bb800c | |||
| f80c28cde7 | |||
| b7e5bedf4b | |||
| 994e426d1e | |||
| c5bf0d94f9 | |||
| 8ad9ada015 | |||
| e854261124 | |||
| 0370b4eb55 | |||
| 5eef5b4e31 | |||
| 61d9c051de | |||
| 5ea1bff1e6 | |||
| bc035cff6a | |||
| f2c008602d | |||
| b8073bca0f | |||
| c30a61441b | |||
| f0f279eec5 | |||
| 464cc8449e | |||
| ee2e57d702 | |||
| a2a5fb166e | |||
| f59f5dfaa3 | |||
| 13f12227bd | |||
| c9db21063b | |||
| 125184f300 | |||
| 8068517808 | |||
| a4f6bbda5e | |||
| 8b2598fdac | |||
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 | |||
| 6a6c6d6502 | |||
| 2d98c5d311 | |||
| 03b13f8ab0 | |||
| 6e74ad942f | |||
| 0f85f247b6 |
@@ -0,0 +1,48 @@
|
||||
name: Bug report
|
||||
description: File a bug reports regarding the firmware.
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
|
||||
Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug.
|
||||
description: "A clear and concise description of what the bug is."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: "How can this bug be reproduced?"
|
||||
placeholder: |
|
||||
1. Switch on...
|
||||
2. Press button '....'
|
||||
3. Wait for the end of the universe
|
||||
4. It burns
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: "A clear and concise description of what you expected to happen"
|
||||
placeholder: |
|
||||
1. Generates file on...
|
||||
2. I get a cheeseburger...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: target
|
||||
attributes:
|
||||
label: Environment/versions
|
||||
description: Specify extra details about versions and environments affected
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Feature Request
|
||||
description: For feature requests regarding the firmware.
|
||||
labels: ['feature request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
|
||||
Please try the latest nightly release before submitting this, make sure it has not been implemented already. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: "Description of the feature you're suggesting."
|
||||
description: |
|
||||
Please describe your feature request in as many details as possible.
|
||||
- Describe what it should do.
|
||||
- Note whether it is to extend existing functionality or introduce new functionality.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve the software
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
----
|
||||
|
||||
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases)
|
||||
|
||||
----
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Tap on '....'
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Affected versions**
|
||||
Please write any difference related with the Expected behavior, on the following versions:
|
||||
* Latest Stable release:
|
||||
* Latest Nightly release:
|
||||
* Previous working release:
|
||||
|
||||
**Additional**
|
||||
If the bug is difficult to explain, additionally to the text please include images and videos.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Need more help?
|
||||
url: https://discord.com/invite/tuwVMv3
|
||||
about: For any question that does not fit as an issue, check our discord.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen. Remember that adding stuff is always possible, but time is a limited resource for everyone. Check the wiki for more information how to compile the firmware and try to explore modifying the code yourself.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional**
|
||||
If the suggestion is difficult to explain, additionally to the text please include images and videos.
|
||||
@@ -71,11 +71,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
|
||||
@@ -55,11 +55,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.9.0
|
||||
v1.9.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.9.1
|
||||
v2.0.0
|
||||
|
||||
+9
-2
@@ -55,7 +55,7 @@ CMakeFiles/
|
||||
# Debugging
|
||||
.gdbinit*
|
||||
|
||||
# Editor/IDE files
|
||||
# Editor/ IDE files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
@@ -68,8 +68,15 @@ CMakeFiles/
|
||||
.DS_Store
|
||||
/firmware/CMakeCache.txt
|
||||
|
||||
# Python env
|
||||
# Python env/ venv and cache
|
||||
env/
|
||||
venv/
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Other
|
||||
*.bak
|
||||
|
||||
# generated bitmap arr file
|
||||
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
|
||||
/firmware/tools/bitmap.hpp
|
||||
|
||||
@@ -72,5 +72,12 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
|
||||
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
||||
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
||||
|
||||
find_program(CCACHE "ccache")
|
||||
if(CCACHE)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
||||
endif(CCACHE)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(firmware)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
> [!IMPORTANT]
|
||||
> this repository **has just been moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please keep this in mind to **update your links** accordingly.
|
||||
> [!WARNING]
|
||||
> Recently, we have become aware of certain individuals distributing prepackaged packages of Mayhem for a fee, and we want to bring this to your attention to ensure the integrity and security of our project.
|
||||
>
|
||||
> We would like to clarify that none of the contributors to Mayhem receive any monetary compensation for their efforts. All the work put into this project is driven by passion and hobby, and we are committed to providing the community with an open-source solution that remains free of charge.
|
||||
>
|
||||
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED PACKAGES, YOU'RE BEING SCAMMED.__ Seek a refund from your card company and report the issue immediately.
|
||||
> The only legitimate link leading to our repositories is the organization [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please ensure that any download or access to our software is done exclusively through these trusted sources.
|
||||
|
||||
# PortaPack Mayhem
|
||||
|
||||
@@ -13,11 +18,11 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
||||
|
||||
# What is this?
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||
|
||||
[](https://www.youtube.com/watch?v=H-bqdWfbhpg)
|
||||
[<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="512">](https://grabify.link/C0J6ZR)
|
||||
|
||||
For people familiar with the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware), this one might be interesting:<br>[What is the HackRF One Portapack H2+?](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
||||
[<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="254">](https://grabify.link/5MU2VH) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="254">](https://grabify.link/9UZGEW)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make ppfw
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ if(cpp20_supported)
|
||||
else()
|
||||
set(USE_CPPOPT "-std=c++17")
|
||||
endif()
|
||||
set(USE_CPPOPT "${USE_CPPOPT} -flto -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
||||
set(USE_CPPOPT "${USE_CPPOPT} -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
||||
|
||||
# Enable this if you want the linker to remove unused code and data
|
||||
set(USE_LINK_GC yes)
|
||||
@@ -52,7 +52,7 @@ set(USE_LINK_GC yes)
|
||||
# Linker extra options here.
|
||||
set(USE_LDOPT)
|
||||
|
||||
# Enable this if you want link time optimizations (LTO)
|
||||
# Enable this if you want link time optimizations (LTO) - this flag affects chibios only
|
||||
set(USE_LTO no)
|
||||
|
||||
# If enabled, this option allows to compile the application in THUMB mode.
|
||||
@@ -293,7 +293,7 @@ set(CPPSRC
|
||||
# apps/ui_jammer.cpp
|
||||
# apps/ui_keyfob.cpp
|
||||
# apps/ui_lcr.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_looking_glass_app.cpp
|
||||
apps/ui_mictx.cpp
|
||||
apps/ui_modemsetup.cpp
|
||||
@@ -304,7 +304,7 @@ set(CPPSRC
|
||||
apps/ui_pocsag_tx.cpp
|
||||
apps/ui_rds.cpp
|
||||
apps/ui_recon_settings.cpp
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_remote.cpp
|
||||
apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
@@ -347,10 +347,12 @@ set(CPPSRC
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
|
||||
${EXTCPPSRC}
|
||||
)
|
||||
|
||||
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||
|
||||
list (APPEND CPPSRC ${EXTCPPSRC}) # Append external sources after setting lto flag to internal ones
|
||||
|
||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
|
||||
@@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
namespace app_settings {
|
||||
|
||||
enum class Mode : uint8_t {
|
||||
NO_RF = 0x00,
|
||||
RX = 0x01,
|
||||
TX = 0x02,
|
||||
RX_TX = 0x03, // Both TX/RX
|
||||
|
||||
@@ -64,9 +64,9 @@ static std::string mmsi(
|
||||
|
||||
static std::string mid(
|
||||
const ais::MMSI& mmsi) {
|
||||
std::database db;
|
||||
database db;
|
||||
std::string mid_code = "";
|
||||
std::database::MidDBRecord mid_record = {};
|
||||
database::MidDBRecord mid_record = {};
|
||||
int return_code = 0;
|
||||
|
||||
// Try getting the country name from mids.db using MID code for given MMSI
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "string_format.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "radio.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
@@ -112,10 +113,14 @@ SPECOptionsView::SPECOptionsView(
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
add_children({&label_config,
|
||||
&options_config,
|
||||
&text_speed,
|
||||
&field_speed});
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
&text_speed,
|
||||
&field_speed,
|
||||
&text_rx_cal,
|
||||
&field_rx_iq_phase_cal,
|
||||
});
|
||||
|
||||
options_config.set_selected_index(view->get_spec_bw_index());
|
||||
options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
|
||||
@@ -126,6 +131,14 @@ SPECOptionsView::SPECOptionsView(
|
||||
field_speed.on_change = [this, view](int32_t v) {
|
||||
view->set_spec_trigger(v);
|
||||
};
|
||||
|
||||
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
|
||||
field_rx_iq_phase_cal.set_value(view->get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
|
||||
field_rx_iq_phase_cal.on_change = [this, view](int32_t v) {
|
||||
view->set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
|
||||
};
|
||||
|
||||
view->set_spec_iq_phase_calibration_value(view->get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||
}
|
||||
|
||||
/* AnalogAudioView *******************************************************/
|
||||
@@ -213,6 +226,15 @@ void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
|
||||
receiver_model.set_baseband_bandwidth(bw / 2);
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
|
||||
return iq_phase_calibration_value;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
|
||||
iq_phase_calibration_value = cal_value;
|
||||
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
}
|
||||
|
||||
uint16_t AnalogAudioView::get_spec_trigger() {
|
||||
return spec_trigger;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,16 @@ class SPECOptionsView : public View {
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
Text text_rx_cal{
|
||||
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
|
||||
"Rx_IQ_CAL "};
|
||||
NumberField field_rx_iq_phase_cal{
|
||||
{28 * 8, 0 * 16},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
};
|
||||
|
||||
class AnalogAudioView : public View {
|
||||
@@ -152,14 +162,22 @@ class AnalogAudioView : public View {
|
||||
uint16_t get_spec_trigger();
|
||||
void set_spec_trigger(uint16_t trigger);
|
||||
|
||||
uint8_t get_spec_iq_phase_calibration_value();
|
||||
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio", app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
"rx_audio",
|
||||
app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency,
|
||||
{
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||
}};
|
||||
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -51,7 +52,7 @@ void SoundBoardView::stop() {
|
||||
|
||||
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
stop();
|
||||
// progressbar.set_value(0);
|
||||
progressbar.set_value(0);
|
||||
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
if (check_random.value()) {
|
||||
@@ -80,9 +81,16 @@ void SoundBoardView::file_error() {
|
||||
}
|
||||
|
||||
void SoundBoardView::start_tx(const uint32_t id) {
|
||||
if (file_list.empty()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
stop();
|
||||
|
||||
@@ -93,11 +101,12 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
playing_id = id;
|
||||
|
||||
// progressbar.set_max(reader->sample_count());
|
||||
progressbar.set_max(reader->sample_count());
|
||||
|
||||
// button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
@@ -113,12 +122,13 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
0 // LSB
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
bits_per_sample,
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
@@ -126,7 +136,8 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
audio::output::start();
|
||||
if (tone_key_index == 0)
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -138,8 +149,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
}*/
|
||||
|
||||
void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
||||
(void)progress; // avoid warning
|
||||
// progressbar.set_value(progress);
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void SoundBoardView::on_select_entry() {
|
||||
@@ -164,7 +174,7 @@ void SoundBoardView::refresh_list() {
|
||||
|
||||
if (entry_extension == ".WAV") {
|
||||
if (reader->open(u"/WAV/" + entry.path().native())) {
|
||||
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
|
||||
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
|
||||
// sounds[c].ms_duration = reader->ms_duration();
|
||||
// sounds[c].path = u"WAV/" + entry.path().native();
|
||||
if (count >= (page - 1) * 100 && count < page * 100) {
|
||||
@@ -203,7 +213,7 @@ void SoundBoardView::refresh_list() {
|
||||
|
||||
for (size_t n = 0; n < file_list.size(); n++) {
|
||||
menu_view.add_item({file_list[n].string().substr(0, 30),
|
||||
ui::Color::white(),
|
||||
ui::Color::dark_magenta(),
|
||||
nullptr,
|
||||
[this](KeyEvent) {
|
||||
on_select_entry();
|
||||
@@ -230,8 +240,9 @@ SoundBoardView::SoundBoardView(
|
||||
&options_tone_key,
|
||||
//&text_title,
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&progressbar,
|
||||
&field_volume,
|
||||
&text_volume_disabled,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -257,6 +268,13 @@ SoundBoardView::SoundBoardView(
|
||||
tone_keys_populate(options_tone_key);
|
||||
options_tone_key.set_selected_index(0);
|
||||
|
||||
text_volume_disabled.hidden(true);
|
||||
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
bool tone_key_enabled = (index != 0);
|
||||
text_volume_disabled.hidden(!tone_key_enabled);
|
||||
field_volume.hidden(tone_key_enabled);
|
||||
};
|
||||
|
||||
check_loop.set_value(false);
|
||||
check_random.set_value(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -103,7 +104,7 @@ class SoundBoardView : public View {
|
||||
"<="};
|
||||
|
||||
Text page_info{
|
||||
{0, 30 * 8 - 4, 30 * 8, 16}};
|
||||
{0, 29 * 8, 30 * 8, 16}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 175},
|
||||
@@ -128,6 +129,9 @@ class SoundBoardView : public View {
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 180}};
|
||||
Text text_volume_disabled{
|
||||
{28 * 8, 180, 3 * 8, 16},
|
||||
"--"};
|
||||
|
||||
Checkbox check_loop{
|
||||
{0, 25 * 8 + 4},
|
||||
@@ -139,9 +143,8 @@ class SoundBoardView : public View {
|
||||
6,
|
||||
"Random"};
|
||||
|
||||
// ProgressBar progressbar {
|
||||
// { 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
|
||||
// };
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -127,8 +128,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
text_icao_address.set(entry.icao_str);
|
||||
|
||||
// Try getting the aircraft information from icao24.db
|
||||
std::database db{};
|
||||
std::database::AircraftDBRecord aircraft_record;
|
||||
database db{};
|
||||
database::AircraftDBRecord aircraft_record;
|
||||
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
@@ -199,6 +200,10 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
}
|
||||
break;
|
||||
|
||||
case DATABASE_RECORD_NOT_FOUND:
|
||||
// Defaults should be filled by the constructor
|
||||
break;
|
||||
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_manufacturer.set("No icao24.db file");
|
||||
break;
|
||||
@@ -233,24 +238,6 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
&button_aircraft_details,
|
||||
&button_see_map});
|
||||
|
||||
// The following won't change for a given airborne aircraft.
|
||||
// Try getting the airline's name from airlines.db.
|
||||
// NB: Only works once callsign has been read and won't be updated.
|
||||
std::database db;
|
||||
std::database::AirlinesDBRecord airline_record;
|
||||
std::string airline_code = entry_.callsign.substr(0, 3);
|
||||
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
||||
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
text_airline.set(airline_record.airline);
|
||||
text_country.set(airline_record.country);
|
||||
break;
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_airline.set("No airlines.db file");
|
||||
break;
|
||||
}
|
||||
|
||||
text_icao_address.set(entry_.icao_str);
|
||||
|
||||
button_aircraft_details.on_select = [this, &nav](Button&) {
|
||||
@@ -330,6 +317,31 @@ void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
|
||||
}
|
||||
|
||||
void ADSBRxDetailsView::refresh_ui() {
|
||||
// The following won't change for a given airborne aircraft.
|
||||
// Try getting the airline's name from airlines.db.
|
||||
if (!airline_checked && !entry_.callsign.empty()) {
|
||||
airline_checked = true;
|
||||
|
||||
database db;
|
||||
database::AirlinesDBRecord airline_record;
|
||||
std::string airline_code = entry_.callsign.substr(0, 3);
|
||||
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
||||
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
text_airline.set(airline_record.airline);
|
||||
text_country.set(airline_record.country);
|
||||
break;
|
||||
case DATABASE_RECORD_NOT_FOUND:
|
||||
// text_airline.set("-"); // It's what it is constructed with
|
||||
// text_country.set("-"); // It's what it is constructed with
|
||||
break;
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_airline.set("No airlines.db file");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto age = entry_.age;
|
||||
if (age < 60)
|
||||
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -280,6 +281,7 @@ class ADSBRxDetailsView : public View {
|
||||
// NB: Keeping a copy so that it doesn't end up dangling
|
||||
// if removed from the recent entries list.
|
||||
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
|
||||
bool airline_checked{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
|
||||
|
||||
@@ -101,6 +101,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 3) {
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 4) {
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -221,7 +221,8 @@ class APRSRxView : public View {
|
||||
{{"NA ", 0},
|
||||
{"EUR", 1},
|
||||
{"AUS", 2},
|
||||
{"NZ ", 3}}};
|
||||
{"NZ ", 3},
|
||||
{"ISS", 4}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -322,6 +324,11 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
|
||||
last_delta = delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
@@ -404,6 +411,8 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
|
||||
painter.draw_string({5 * 8, 12 * 16}, Styles::light_grey, to_string_dec_int(last_delta, 3));
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::on_frame_sync() {
|
||||
@@ -437,43 +446,66 @@ void DebugControlsView::focus() {
|
||||
|
||||
/* DebugPeripheralsMenuView **********************************************/
|
||||
|
||||
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
|
||||
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
void DebugPeripheralsMenuView::on_populate() {
|
||||
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
|
||||
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
|
||||
add_items({
|
||||
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
|
||||
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
|
||||
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
||||
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
|
||||
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [this, max283x]() { nav_.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
|
||||
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
||||
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
/* DebugReboot **********************************************/
|
||||
|
||||
DebugReboot::DebugReboot(NavigationView& nav) {
|
||||
(void)nav;
|
||||
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 0);
|
||||
|
||||
while (1)
|
||||
__WFE();
|
||||
}
|
||||
|
||||
void DebugReboot::on_populate() {
|
||||
}
|
||||
|
||||
/* DebugMenuView *********************************************************/
|
||||
|
||||
DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
DebugMenuView::DebugMenuView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
void DebugMenuView::on_populate() {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
|
||||
{"Memory Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryDumpView>(); }},
|
||||
{"Memory Usage", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
|
||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [this]() { nav_.push<DebugControlsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { stack_dump(); }},
|
||||
{"Memory Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
|
||||
//{"Memory Usage", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryView>(); }},
|
||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
|
||||
//{ "Radio State", ui::Color::white(), nullptr, [this](){ nav_.push<NotImplementedView>(); } },
|
||||
{"Reboot", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
|
||||
});
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav_)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
/* DebugMemoryDumpView *********************************************************/
|
||||
@@ -552,6 +584,7 @@ void DebugPmemView::update() {
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
@@ -561,10 +594,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
nav_.pop();
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
pen_color = semirand();
|
||||
pen_color = std::rand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -584,17 +617,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t DebugScreenTest::semirand() {
|
||||
static uint64_t seed{0x0102030405060708};
|
||||
seed = seed * 137;
|
||||
seed = (seed >> 1) | ((seed & 0x01) << 63);
|
||||
return (uint16_t)seed;
|
||||
}
|
||||
|
||||
void DebugScreenTest::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
|
||||
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
|
||||
pen_color = semirand();
|
||||
pen_color = std::rand();
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -251,18 +253,21 @@ class ControlsSwitchesWidget : public Widget {
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect},
|
||||
key_event_mask(0),
|
||||
long_press_key_event_mask{0} {
|
||||
long_press_key_event_mask{0},
|
||||
last_delta{0} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void on_show() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
uint8_t key_event_mask;
|
||||
uint8_t long_press_key_event_mask;
|
||||
EncoderEvent last_delta;
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
@@ -284,6 +289,7 @@ class DebugControlsView : public View {
|
||||
private:
|
||||
Labels labels{
|
||||
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
|
||||
{{0 * 8, 11 * 16}, "Dial:", Color::grey()},
|
||||
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
|
||||
|
||||
ControlsSwitchesWidget switches_widget{
|
||||
@@ -385,7 +391,6 @@ class DebugScreenTest : public View {
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
uint16_t semirand();
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
@@ -418,12 +423,28 @@ class DebugPeripheralsMenuView : public BtnGridView {
|
||||
public:
|
||||
DebugPeripheralsMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Peripherals"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class DebugReboot : public BtnGridView {
|
||||
public:
|
||||
DebugReboot(NavigationView& nav);
|
||||
|
||||
private:
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class DebugMenuView : public BtnGridView {
|
||||
public:
|
||||
DebugMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Debug"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -37,23 +38,29 @@ DfuMenu::DfuMenu(NavigationView& nav)
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8});
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10});
|
||||
}
|
||||
|
||||
void DfuMenu::paint(Painter& painter) {
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
size_t m0_fragmented_free_space = 0;
|
||||
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
||||
|
||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||
text_info_line_2.set(to_string_dec_uint((uint32_t)get_free_stack_space(), 6));
|
||||
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
|
||||
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
||||
text_info_line_3.set(to_string_dec_uint(m0_fragments, 6));
|
||||
text_info_line_4.set(to_string_dec_uint((uint32_t)get_free_stack_space(), 6));
|
||||
text_info_line_5.set(to_string_dec_uint(utilisation, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
constexpr auto lines = 10 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -48,14 +49,16 @@ class DfuMenu : public View {
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 11 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Performance"};
|
||||
|
||||
Labels labels{
|
||||
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 heap:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M4 heap:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 core:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 heap:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 frags:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M0 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M0 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 heap:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -65,6 +68,8 @@ class DfuMenu : public View {
|
||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
|
||||
@@ -21,15 +21,62 @@
|
||||
*/
|
||||
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
|
||||
// Firmware image validation
|
||||
static const char* hackrf_magic = "HACKRFFW";
|
||||
#define FIRMWARE_INFO_AREA_OFFSET 0x400
|
||||
#define FIRST_CHECKSUM_NIGHTLY 240125
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
File firmware_file;
|
||||
bool require_checksum{false};
|
||||
uint32_t read_buffer[128];
|
||||
uint32_t checksum{FLASH_CHECKSUM_ERROR}; // initializing to invalid checksum in case file can't be read
|
||||
|
||||
static_assert((FIRMWARE_INFO_AREA_OFFSET % sizeof(read_buffer)) == 0, "Read buffer size must divide evenly into FIRMWARE_INFO_AREA_OFFSET");
|
||||
|
||||
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
|
||||
auto result = firmware_file.open(path.c_str());
|
||||
if (!result.is_valid()) {
|
||||
checksum = 0;
|
||||
for (uint32_t offset = 0; offset < FLASH_ROM_SIZE; offset += sizeof(read_buffer)) {
|
||||
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
|
||||
|
||||
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
|
||||
// File was smaller than 1MB:
|
||||
// If version is such that the file SHOULD have been 1MB, call it a checksum error (otherwise say it's OK).
|
||||
checksum = (require_checksum) ? FLASH_CHECKSUM_ERROR : FLASH_EXPECTED_CHECKSUM;
|
||||
break;
|
||||
}
|
||||
|
||||
// Did we just read the firmware info area?
|
||||
if (offset == FIRMWARE_INFO_AREA_OFFSET) {
|
||||
// If there's no info area (missing HACKRFFW signature), it could be an ancient FW version (so skipping check)
|
||||
if (memcmp(read_buffer, hackrf_magic, 8) == 0) {
|
||||
char* version_string = (char*)&read_buffer[4];
|
||||
|
||||
// Require a 1MB firmware image with a valid checksum if release version >=v2.x or nightly >n_240125
|
||||
if (((version_string[0] == 'v') && (std::atoi(&version_string[1]) >= 2)) ||
|
||||
((version_string[0] == 'n') && (version_string[1] == '_') && (std::atoi(&version_string[2]) >= FIRST_CHECKSUM_NIGHTLY)))
|
||||
require_checksum = true;
|
||||
}
|
||||
}
|
||||
|
||||
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
|
||||
}
|
||||
}
|
||||
return (checksum == FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
|
||||
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&labels,
|
||||
@@ -85,9 +132,8 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
||||
//
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -98,22 +144,22 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), fileName);
|
||||
});
|
||||
if (res.string().empty()) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Painter painter;
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
path = extract_tar(u'/' + path, painter).native();
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str())) {
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
return false;
|
||||
}
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -126,6 +172,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
return true; // fixes compiler warning (line should not be reached due to halt)
|
||||
}
|
||||
|
||||
void FlashUtilityView::focus() {
|
||||
|
||||
@@ -32,8 +32,15 @@
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#define FLASH_ROM_SIZE 1048576
|
||||
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path);
|
||||
|
||||
class FlashUtilityView : public View {
|
||||
public:
|
||||
FlashUtilityView(NavigationView& nav);
|
||||
@@ -41,6 +48,7 @@ class FlashUtilityView : public View {
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
bool flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -55,9 +63,9 @@ class FlashUtilityView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||
};
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ void FrequencyManagerView::on_edit_freq() {
|
||||
|
||||
void FrequencyManagerView::on_edit_desc() {
|
||||
temp_buffer_ = current_entry().description;
|
||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, [this](std::string& new_desc) {
|
||||
auto entry = current_entry();
|
||||
entry.description = std::move(new_desc);
|
||||
db_.replace_entry(current_index(), entry);
|
||||
|
||||
@@ -40,8 +40,6 @@ class FreqManBaseView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
static constexpr size_t desc_edit_max = 0x80;
|
||||
|
||||
protected:
|
||||
using options_t = OptionsField::options_t;
|
||||
|
||||
@@ -63,11 +61,11 @@ class FreqManBaseView : public View {
|
||||
|
||||
/* The top section (category) is 20px tall. */
|
||||
Labels label_category{
|
||||
{{0, 2}, "Category:", Color::light_grey()}};
|
||||
{{0, 2}, "F:", Color::light_grey()}};
|
||||
|
||||
OptionsField options_category{
|
||||
{9 * 8, 2},
|
||||
14 /* length */,
|
||||
{3 * 8, 2},
|
||||
20 /* length */,
|
||||
{}};
|
||||
|
||||
FreqManUIList freqlist_view{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "oversample.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
@@ -39,6 +40,8 @@ void LevelView::focus() {
|
||||
}
|
||||
|
||||
LevelView::~LevelView() {
|
||||
// reset performance counters request to default
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -58,12 +61,16 @@ LevelView::LevelView(NavigationView& nav)
|
||||
&text_ctcss,
|
||||
&freq_stats_rssi,
|
||||
&freq_stats_db,
|
||||
&freq_stats_rx,
|
||||
&audio_mode,
|
||||
&peak_mode,
|
||||
&rssi,
|
||||
&rssi_graph});
|
||||
|
||||
// activate vertical bar mode
|
||||
rssi.set_vertical_rssi(true);
|
||||
// activate counters for RxSat
|
||||
shared_memory.request_m4_performance_counter = 2;
|
||||
|
||||
change_mode(NFM_MODULATION); // Start on AM
|
||||
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
|
||||
@@ -139,13 +146,15 @@ LevelView::LevelView(NavigationView& nav)
|
||||
freqman_set_step_option_short(step_mode);
|
||||
freq_stats_rssi.set_style(&Styles::white);
|
||||
freq_stats_db.set_style(&Styles::white);
|
||||
freq_stats_rx.set_style(&Styles::white);
|
||||
}
|
||||
|
||||
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
static int16_t last_max_db = -1000;
|
||||
static int16_t last_min_rssi = -1000;
|
||||
static int16_t last_avg_rssi = -1000;
|
||||
static int16_t last_max_rssi = -1000;
|
||||
static int16_t last_max_db = 0;
|
||||
static uint8_t last_min_rssi = 0;
|
||||
static uint8_t last_avg_rssi = 0;
|
||||
static uint8_t last_max_rssi = 0;
|
||||
static uint8_t last_rx_sat = 0;
|
||||
|
||||
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
|
||||
|
||||
@@ -159,8 +168,31 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
last_min_rssi = rssi_graph.get_graph_min();
|
||||
last_avg_rssi = rssi_graph.get_graph_avg();
|
||||
last_max_rssi = rssi_graph.get_graph_max();
|
||||
freq_stats_rssi.set("RSSI: " + to_string_dec_int(last_min_rssi) + "/" + to_string_dec_int(last_avg_rssi) + "/" + to_string_dec_int(last_max_rssi) + ",dt: " + to_string_dec_int(rssi_graph.get_graph_delta()));
|
||||
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi) + ", dt: " + to_string_dec_uint(rssi_graph.get_graph_delta()));
|
||||
}
|
||||
// refresh sat
|
||||
uint8_t rx_sat = ((uint32_t)shared_memory.m4_performance_counter) * 100 / 127;
|
||||
if (last_rx_sat != rx_sat) {
|
||||
last_rx_sat = rx_sat;
|
||||
freq_stats_rx.set("RxSat: " + to_string_dec_uint(rx_sat) + "%");
|
||||
uint8_t br = 0;
|
||||
uint8_t bg = 0;
|
||||
uint8_t bb = 0;
|
||||
if (rx_sat <= 80) {
|
||||
bg = (255 * rx_sat) / 80;
|
||||
bb = 255 - bg;
|
||||
} else if (rx_sat > 80) {
|
||||
br = (255 * (rx_sat - 80)) / 20;
|
||||
bg = 255 - br;
|
||||
}
|
||||
Style style_freq_stats_rx{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {br, bg, bb},
|
||||
.foreground = {255, 255, 255},
|
||||
};
|
||||
freq_stats_rx.set_style(&style_freq_stats_rx);
|
||||
}
|
||||
|
||||
} /* on_statistic_updates */
|
||||
|
||||
size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
|
||||
@@ -62,8 +62,6 @@ class LevelView : public View {
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void set_display_freq(int64_t freq);
|
||||
|
||||
// TODO: needed?
|
||||
int32_t db{0};
|
||||
rf::Frequency freq_ = {0};
|
||||
|
||||
Labels labels{
|
||||
@@ -118,12 +116,12 @@ class LevelView : public View {
|
||||
|
||||
// RSSI: XX/XX/XXX,dt: XX
|
||||
Text freq_stats_rssi{
|
||||
{0 * 8, 3 * 16 + 4, 22 * 8, 14},
|
||||
{0 * 8, 3 * 16 + 4, 22 * 8, 1 * 16},
|
||||
};
|
||||
|
||||
// Power: -XXX db
|
||||
Text freq_stats_db{
|
||||
{0 * 8, 4 * 16 + 4, 14 * 8, 14},
|
||||
{0 * 8, 4 * 16 + 4, 15 * 8, 1 * 16},
|
||||
};
|
||||
|
||||
OptionsField peak_mode{
|
||||
@@ -138,6 +136,7 @@ class LevelView : public View {
|
||||
{"peak:5s", 5000},
|
||||
{"peak:10s", 10000},
|
||||
}};
|
||||
|
||||
OptionsField rssi_resolution{
|
||||
{44 + 20 * 8, 4 * 16 + 4},
|
||||
4,
|
||||
@@ -149,14 +148,19 @@ class LevelView : public View {
|
||||
{"240x", 240},
|
||||
}};
|
||||
|
||||
// RxSat: XX%
|
||||
Text freq_stats_rx{
|
||||
{0 * 8, 5 * 16 + 4, 10 * 8, 1 * 16},
|
||||
};
|
||||
|
||||
RSSIGraph rssi_graph{
|
||||
// 240x320 =>
|
||||
{0, 5 * 16 + 4, 240 - 5 * 8, 320 - (5 * 16 + 4)},
|
||||
{0, 6 * 16 + 4, 240 - 5 * 8, 320 - (6 * 16 + 4)},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
// 240x320 =>
|
||||
{240 - 5 * 8, 5 * 16 + 4, 5 * 8, 320 - (5 * 16 + 4)},
|
||||
{240 - 5 * 8, 6 * 16 + 4, 5 * 8, 320 - (6 * 16 + 4)},
|
||||
};
|
||||
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
|
||||
@@ -338,6 +338,7 @@ GlassView::GlassView(
|
||||
&field_trigger,
|
||||
&button_jump,
|
||||
&button_rst,
|
||||
&field_rx_iq_phase_cal,
|
||||
&freq_stats});
|
||||
|
||||
load_presets(); // Load available presets from TXT files (or default).
|
||||
@@ -474,6 +475,13 @@ GlassView::GlassView(
|
||||
reset_live_view();
|
||||
};
|
||||
|
||||
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
|
||||
field_rx_iq_phase_cal.set_value(get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
|
||||
field_rx_iq_phase_cal.on_change = [this](int32_t v) {
|
||||
set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
|
||||
};
|
||||
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||
|
||||
display.scroll_set_area(109, 319);
|
||||
|
||||
// trigger:
|
||||
@@ -491,6 +499,15 @@ GlassView::GlassView(
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
uint8_t GlassView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
|
||||
return iq_phase_calibration_value;
|
||||
}
|
||||
|
||||
void GlassView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
|
||||
iq_phase_calibration_value = cal_value;
|
||||
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
}
|
||||
|
||||
void GlassView::load_presets() {
|
||||
File presets_file;
|
||||
auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
|
||||
|
||||
@@ -69,6 +69,9 @@ class GlassView : public View {
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
|
||||
uint8_t get_spec_iq_phase_calibration_value();
|
||||
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||
@@ -79,8 +82,9 @@ class GlassView : public View {
|
||||
uint8_t filter_index = 0; // OFF
|
||||
uint8_t trigger = 32;
|
||||
uint8_t mode = LOOKING_GLASS_FASTSCAN;
|
||||
uint8_t live_frequency_view = 0; // Spectrum
|
||||
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
|
||||
uint8_t live_frequency_view = 0; // Spectrum
|
||||
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_glass"sv,
|
||||
app_settings::Mode::RX,
|
||||
@@ -93,6 +97,7 @@ class GlassView : public View {
|
||||
{"scan_mode"sv, &mode},
|
||||
{"freq_view"sv, &live_frequency_view},
|
||||
{"freq_integrate"sv, &live_frequency_integrate},
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||
}};
|
||||
|
||||
struct preset_entry {
|
||||
@@ -160,7 +165,7 @@ class GlassView : public View {
|
||||
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()},
|
||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
|
||||
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
|
||||
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
|
||||
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Color::light_grey()},
|
||||
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}};
|
||||
|
||||
NumberField field_frequency_min{
|
||||
@@ -208,6 +213,14 @@ class GlassView : public View {
|
||||
{7 * 8, 3 * 16, 9 * 8, 16},
|
||||
""};
|
||||
|
||||
NumberField field_rx_iq_phase_cal{
|
||||
{28 * 8, 3 * 16},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_trigger{
|
||||
{4 * 8, 4 * 16},
|
||||
3,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "wm8731.hpp"
|
||||
#include "radio.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -105,6 +107,7 @@ void MicTXView::configure_baseband() {
|
||||
transmitting ? transmitter_model.channel_bandwidth() : 0,
|
||||
mic_gain_x10 / 10.0,
|
||||
shift_bits(), // to be used in dsp_modulate
|
||||
8, // bits per sample
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
|
||||
(mic_mod_index == MIC_MOD_AM),
|
||||
(mic_mod_index == MIC_MOD_DSB),
|
||||
@@ -335,6 +338,7 @@ MicTXView::MicTXView(
|
||||
&field_rxlna,
|
||||
&field_rxvga,
|
||||
&field_rxamp,
|
||||
&field_tx_iq_phase_cal,
|
||||
&tx_button,
|
||||
&tx_icon});
|
||||
|
||||
@@ -367,6 +371,14 @@ MicTXView::MicTXView(
|
||||
receiver_model.set_rf_amp(v);
|
||||
};
|
||||
|
||||
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
field_tx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
|
||||
field_tx_iq_phase_cal.set_value(iq_phase_calibration_value);
|
||||
field_tx_iq_phase_cal.on_change = [this](int32_t v) {
|
||||
iq_phase_calibration_value = v;
|
||||
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
};
|
||||
|
||||
options_gain.on_change = [this](size_t, int32_t v) {
|
||||
mic_gain_x10 = v;
|
||||
configure_baseband();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -113,6 +114,7 @@ class MicTXView : public View {
|
||||
uint32_t va_level{40};
|
||||
uint32_t attack_ms{500};
|
||||
uint32_t decay_ms{1000};
|
||||
uint8_t iq_phase_calibration_value{15};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_mic",
|
||||
app_settings::Mode::RX_TX,
|
||||
@@ -132,6 +134,7 @@ class MicTXView : public View {
|
||||
{"vox"sv, &va_enabled},
|
||||
{"rogerbeep"sv, &rogerbeep_enabled},
|
||||
{"tone_key_index"sv, &tone_key_index},
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
|
||||
}};
|
||||
|
||||
rf::Frequency tx_frequency{0};
|
||||
@@ -160,7 +163,8 @@ class MicTXView : public View {
|
||||
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
|
||||
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
|
||||
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
|
||||
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
|
||||
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()},
|
||||
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", Color::light_grey()}};
|
||||
Labels labels_WM8731{
|
||||
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
|
||||
Labels labels_AK4951{
|
||||
@@ -338,6 +342,14 @@ class MicTXView : public View {
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_tx_iq_phase_cal{
|
||||
{24 * 8, (33 * 8)},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button tx_button{
|
||||
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
||||
"PTT TX",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyleft (ↄ) 2022 NotPike
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -389,7 +390,7 @@ PlaylistView::PlaylistView(
|
||||
ensure_directory(u"PLAYLIST");
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(100'000'000);
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
if (current())
|
||||
current()->metadata.center_frequency = f;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -56,7 +57,7 @@ class PlaylistView : public View {
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_playlist", app_settings::Mode::TX};
|
||||
"tx_replay", app_settings::Mode::TX};
|
||||
|
||||
// More header == less spectrum view.
|
||||
static constexpr ui::Dim header_height = 6 * 16;
|
||||
|
||||
@@ -49,7 +49,13 @@ namespace fs = std::filesystem;
|
||||
namespace ui {
|
||||
|
||||
void ReconView::reload_restart_recon() {
|
||||
// force reload of current
|
||||
change_mode(field_mode.selected_index_value());
|
||||
uint8_t previous_index = current_index;
|
||||
reset_indexes();
|
||||
frequency_file_load();
|
||||
current_index = previous_index;
|
||||
handle_retune();
|
||||
if (frequency_list.size() > 0) {
|
||||
if (fwd) {
|
||||
button_dir.set_text("FW>");
|
||||
@@ -104,18 +110,19 @@ void ReconView::set_loop_config(bool v) {
|
||||
|
||||
void ReconView::recon_stop_recording(bool exiting) {
|
||||
if (is_recording) {
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
button_audio_app.set_text("RAW");
|
||||
else
|
||||
button_audio_app.set_text("AUDIO");
|
||||
button_audio_app.set_style(&Styles::white);
|
||||
record_view->stop();
|
||||
button_config.set_style(&Styles::white);
|
||||
is_recording = false;
|
||||
// repeater mode
|
||||
if (!exiting && persistent_memory::recon_repeat_recorded()) {
|
||||
start_repeat();
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
button_audio_app.set_text("RAW");
|
||||
// repeater mode
|
||||
if (!exiting && persistent_memory::recon_repeat_recorded()) {
|
||||
start_repeat();
|
||||
}
|
||||
} else {
|
||||
button_audio_app.set_text("AUDIO");
|
||||
}
|
||||
button_audio_app.set_style(&Styles::white);
|
||||
button_config.set_style(&Styles::white);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,7 +1155,7 @@ void ReconView::on_stepper_delta(int32_t v) {
|
||||
}
|
||||
|
||||
size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
if (recon_tx || is_repeat_active())
|
||||
if (recon_tx || is_repeat_active() || is_recording)
|
||||
return 0;
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
@@ -1157,16 +1164,21 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
}
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"RECON_REPEAT.C16", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_as_is(true);
|
||||
} else if (new_mod == SPEC_MODULATION) {
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION) {
|
||||
audio::output::stop();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
}
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"RECON_REPEAT.C16", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_as_is(true);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
}
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO", u"AUDIO",
|
||||
@@ -1314,9 +1326,6 @@ bool ReconView::is_repeat_active() const {
|
||||
|
||||
void ReconView::start_repeat() {
|
||||
// Prepare to send a file.
|
||||
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
|
||||
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
|
||||
|
||||
if (recon_tx == false) {
|
||||
recon_tx = true;
|
||||
|
||||
@@ -1434,6 +1443,12 @@ void ReconView::stop_repeat(const bool do_loop) {
|
||||
} else {
|
||||
repeat_cur_rep = 0;
|
||||
recon_tx = false;
|
||||
if (persistent_memory::recon_repeat_recorded_file_mode() == RECON_REPEAT_AND_KEEP) {
|
||||
// rename file here to keep
|
||||
std::filesystem::path base_path = next_filename_matching_pattern(repeat_rec_path / u"REC_????.*");
|
||||
rename_file(rawfile, base_path.replace_extension(u".C16"));
|
||||
rename_file(rawmeta, base_path.replace_extension(u".TXT"));
|
||||
}
|
||||
reload_restart_recon();
|
||||
progressbar.hidden(true);
|
||||
set_dirty(); // fix progressbar no hiding
|
||||
|
||||
@@ -198,6 +198,9 @@ class ReconView : public View {
|
||||
bool repeat_ready_signal{false};
|
||||
bool recon_tx{false};
|
||||
|
||||
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
|
||||
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
|
||||
|
||||
// Persisted settings.
|
||||
SettingsStore ui_settings{
|
||||
"recon"sv,
|
||||
|
||||
@@ -103,6 +103,7 @@ void ReconSetupViewMore::save() {
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
||||
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
|
||||
persistent_memory::set_recon_repeat_recorded_file_mode(field_repeat_file_mode.selected_index_value());
|
||||
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
|
||||
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
|
||||
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
|
||||
@@ -125,6 +126,7 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&checkbox_auto_record_locked,
|
||||
&checkbox_repeat_recorded,
|
||||
&field_repeat_file_mode,
|
||||
&text_repeat_nb,
|
||||
&field_repeat_nb,
|
||||
&checkbox_repeat_amp,
|
||||
@@ -135,6 +137,7 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
|
||||
// tx options have to be in yellow to inform the users that activating them will make the device transmit
|
||||
checkbox_repeat_recorded.set_style(&Styles::yellow);
|
||||
field_repeat_file_mode.set_style(&Styles::yellow);
|
||||
text_repeat_nb.set_style(&Styles::yellow);
|
||||
field_repeat_nb.set_style(&Styles::yellow);
|
||||
checkbox_repeat_amp.set_style(&Styles::yellow);
|
||||
@@ -150,6 +153,7 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
||||
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
||||
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
|
||||
field_repeat_file_mode.set_selected_index(persistent_memory::recon_repeat_recorded_file_mode());
|
||||
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
|
||||
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
|
||||
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
|
||||
|
||||
@@ -38,16 +38,19 @@
|
||||
#endif
|
||||
#define OneMHz 1000000
|
||||
|
||||
// modes
|
||||
// main app mode
|
||||
#define RECON_MATCH_CONTINUOUS 0
|
||||
#define RECON_MATCH_SPARSE 1
|
||||
|
||||
// repeater mode
|
||||
#define RECON_REPEAT_AND_DELETE 0
|
||||
#define RECON_REPEAT_AND_KEEP 1
|
||||
|
||||
// statistics update interval in ms (change here if the statistics API is changing it's pace)
|
||||
#define STATS_UPDATE_INTERVAL 100
|
||||
|
||||
// maximum lock duration
|
||||
#define RECON_MAX_LOCK_DURATION 9900
|
||||
|
||||
#define RECON_DEF_SQUELCH -14
|
||||
|
||||
// default number of match to have a lock
|
||||
@@ -150,15 +153,21 @@ class ReconSetupViewMore : public View {
|
||||
|
||||
Checkbox checkbox_repeat_recorded{
|
||||
{1 * 8, 162},
|
||||
3,
|
||||
"repeater,"};
|
||||
0,
|
||||
""};
|
||||
|
||||
OptionsField field_repeat_file_mode{
|
||||
{4 * 8 + 3, 165},
|
||||
13,
|
||||
{{"repeat,delete", RECON_REPEAT_AND_DELETE},
|
||||
{"repeat,keep ", RECON_REPEAT_AND_KEEP}}};
|
||||
|
||||
Text text_repeat_nb{
|
||||
{14 * 8, 165, 3 * 8, 22},
|
||||
{20 * 8, 165, 3 * 8, 22},
|
||||
"nb:"};
|
||||
|
||||
NumberField field_repeat_nb{
|
||||
{17 * 8, 165},
|
||||
{23 * 8, 165},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -42,6 +45,7 @@ namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
@@ -81,6 +85,7 @@ SetDateTimeView::SetDateTimeView(
|
||||
&field_second,
|
||||
&text_weekday,
|
||||
&text_day_of_year,
|
||||
&text_in_dst_range,
|
||||
&checkbox_dst_enable,
|
||||
&options_dst_start_which,
|
||||
&options_dst_start_weekday,
|
||||
@@ -100,18 +105,25 @@ SetDateTimeView::SetDateTimeView(
|
||||
options_dst_start_month.set_options(month_options);
|
||||
options_dst_end_month.set_options(month_options);
|
||||
|
||||
const auto dst_changed_fn = [this](size_t, uint32_t) {
|
||||
handle_date_field_update();
|
||||
};
|
||||
|
||||
const auto date_changed_fn = [this](int32_t) {
|
||||
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
|
||||
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
|
||||
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
|
||||
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
|
||||
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
|
||||
handle_date_field_update();
|
||||
};
|
||||
|
||||
field_year.on_change = date_changed_fn;
|
||||
field_month.on_change = date_changed_fn;
|
||||
field_day.on_change = date_changed_fn;
|
||||
|
||||
options_dst_start_which.on_change = dst_changed_fn;
|
||||
options_dst_start_weekday.on_change = dst_changed_fn;
|
||||
options_dst_start_month.on_change = dst_changed_fn;
|
||||
options_dst_end_which.on_change = dst_changed_fn;
|
||||
options_dst_end_weekday.on_change = dst_changed_fn;
|
||||
options_dst_end_month.on_change = dst_changed_fn;
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtc_time::now(datetime);
|
||||
SetDateTimeModel model{
|
||||
@@ -146,6 +158,17 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
|
||||
}
|
||||
|
||||
SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value()),
|
||||
.dst = dst_collect()};
|
||||
}
|
||||
|
||||
pmem::dst_config_t SetDateTimeView::dst_collect() {
|
||||
pmem::dst_config_t dst;
|
||||
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
|
||||
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
|
||||
@@ -154,14 +177,16 @@ SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
|
||||
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
|
||||
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value()),
|
||||
.dst = dst};
|
||||
return dst;
|
||||
}
|
||||
|
||||
void SetDateTimeView::handle_date_field_update() {
|
||||
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
|
||||
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
|
||||
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
|
||||
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
|
||||
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
|
||||
text_in_dst_range.set(checkbox_dst_enable.value() && rtc_time::dst_test_date_range(field_year.value(), doy, dst_collect()) ? "DST" : "");
|
||||
}
|
||||
|
||||
/* SetRadioView ******************************************/
|
||||
@@ -293,6 +318,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_fake_brightness,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
@@ -326,6 +352,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_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -352,6 +379,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_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
@@ -586,6 +614,8 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
pmem::cache::defaults();
|
||||
// Refresh status bar
|
||||
send_system_refresh();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -655,13 +685,16 @@ void SetQRCodeView::focus() {
|
||||
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_encoder_dial_sensitivity,
|
||||
&field_encoder_rate_multiplier,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
|
||||
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());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -726,28 +759,109 @@ void SetConfigModeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetFakeBrightnessView ************************************/
|
||||
|
||||
SetFakeBrightnessView::SetFakeBrightnessView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_brightness_switch});
|
||||
|
||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
||||
|
||||
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());
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetFakeBrightnessView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetMenuColorView ************************************/
|
||||
|
||||
void SetMenuColorView::paint_sample() {
|
||||
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
|
||||
button_sample.set_bg_color(c);
|
||||
}
|
||||
|
||||
SetMenuColorView::SetMenuColorView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_sample,
|
||||
&field_red_level,
|
||||
&field_green_level,
|
||||
&field_blue_level,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
button_sample.set_focusable(false);
|
||||
|
||||
Color c = pmem::menu_color();
|
||||
field_red_level.set_value(c.r());
|
||||
field_green_level.set_value(c.g());
|
||||
field_blue_level.set_value(c.b());
|
||||
paint_sample();
|
||||
|
||||
const auto color_changed_fn = [this](int32_t) {
|
||||
paint_sample();
|
||||
};
|
||||
field_red_level.on_change = color_changed_fn;
|
||||
field_green_level.on_change = color_changed_fn;
|
||||
field_blue_level.on_change = color_changed_fn;
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
|
||||
pmem::set_menu_color(c);
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetMenuColorView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
|
||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [&nav]() { nav.push<SetConfigModeView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
|
||||
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
|
||||
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
|
||||
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
|
||||
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
});
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
void SettingsMenuView::on_populate() {
|
||||
if (pmem::show_gui_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>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<TouchCalibrationView>(); }},
|
||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push<SetConfigModeView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
|
||||
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetEncoderDialView>(); }},
|
||||
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
|
||||
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
|
||||
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
|
||||
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetFakeBrightnessView>(); }},
|
||||
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -126,6 +129,9 @@ class SetDateTimeView : public View {
|
||||
Text text_day_of_year{
|
||||
{26 * 8, 6 * 16, 3 * 8, 16},
|
||||
""};
|
||||
Text text_in_dst_range{
|
||||
{17 * 8, 7 * 16, 3 * 8, 16},
|
||||
""};
|
||||
|
||||
Checkbox checkbox_dst_enable{
|
||||
{2 * 8, 9 * 16},
|
||||
@@ -171,6 +177,8 @@ class SetDateTimeView : public View {
|
||||
|
||||
void form_init(const SetDateTimeModel& model);
|
||||
SetDateTimeModel form_collect();
|
||||
portapack::persistent_memory::dst_config_t dst_collect();
|
||||
void handle_date_field_update();
|
||||
};
|
||||
|
||||
struct SetFrequencyCorrectionModel {
|
||||
@@ -315,39 +323,43 @@ class SetUIView : public View {
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{7 * 8, 14 * 16 + 2, 16, 16},
|
||||
{6 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{9 * 8, 14 * 16 + 2, 16, 16},
|
||||
{8 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{11 * 8, 14 * 16 + 2, 16, 16},
|
||||
{10 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
{12 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
{14 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{17 * 8, 14 * 16 + 2, 8, 16},
|
||||
{16 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{18 * 8, 14 * 16 + 2, 16, 16},
|
||||
{17 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{22 * 8, 14 * 16 + 2, 16, 16},
|
||||
{23 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
@@ -545,6 +557,7 @@ class SetQRCodeView : public View {
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::encoder_dial_sensitivity;
|
||||
using portapack::persistent_memory::encoder_rate_multiplier;
|
||||
|
||||
class SetEncoderDialView : public View {
|
||||
public:
|
||||
@@ -556,18 +569,30 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts how many steps to", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "change the encoder value.", Color::light_grey()},
|
||||
{{2 * 8, 4 * 16}, "Dial sensitivity:", Color::light_grey()},
|
||||
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "rotation position (number of", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "steps per full rotation):", Color::light_grey()},
|
||||
{{2 * 8, 5 * 16}, "Dial sensitivity:", Color::light_grey()},
|
||||
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
|
||||
{{1 * 8, 9 * 16}, "rotation rate (default 1", Color::light_grey()},
|
||||
{{1 * 8, 10 * 16}, "means no rate dependency):", Color::light_grey()},
|
||||
{{3 * 8, 12 * 16}, "Rate multiplier:", Color::light_grey()},
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 4 * 16},
|
||||
{20 * 8, 5 * 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},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -667,11 +692,112 @@ class SetConfigModeView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::fake_brightness_level_options;
|
||||
|
||||
class SetFakeBrightnessView : public View {
|
||||
public:
|
||||
SetFakeBrightnessView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Brightness"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Limits screen brightness", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Color::light_grey()},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Color::light_grey()},
|
||||
};
|
||||
|
||||
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"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetMenuColorView : public View {
|
||||
public:
|
||||
SetMenuColorView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Menu Color"; };
|
||||
|
||||
private:
|
||||
void paint_sample();
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Color::light_grey()},
|
||||
{{2 * 8, 8 * 16}, "Red Level:", Color::light_grey()},
|
||||
{{2 * 8, 9 * 16}, "Green Level:", Color::light_grey()},
|
||||
{{2 * 8, 10 * 16}, "Blue Level:", Color::light_grey()},
|
||||
};
|
||||
|
||||
NewButton button_sample{
|
||||
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
|
||||
"New Color",
|
||||
&bitmap_icon_brightness,
|
||||
};
|
||||
|
||||
NumberField field_red_level{
|
||||
{15 * 8, 8 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_green_level{
|
||||
{15 * 8, 9 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_blue_level{
|
||||
{15 * 8, 10 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
std::string title() const override { return "Settings"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -424,6 +425,9 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
&text_size,
|
||||
});
|
||||
|
||||
text_position.set_style(&Styles::bg_dark_blue);
|
||||
text_size.set_style(&Styles::bg_dark_blue);
|
||||
|
||||
viewer.set_font_zoom(enable_zoom);
|
||||
|
||||
viewer.on_select = [this]() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -22,11 +23,12 @@
|
||||
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ViewWavView::update_scale(int32_t new_scale) {
|
||||
@@ -37,9 +39,24 @@ void ViewWavView::update_scale(int32_t new_scale) {
|
||||
}
|
||||
|
||||
void ViewWavView::refresh_waveform() {
|
||||
// NB: We can't read from the file to update the waveform when playback is in progress, so defer til playback done.
|
||||
// (This only happens if the user messes with position or scale fields while playback is occurring)
|
||||
if (playback_in_progress) {
|
||||
waveform_update_needed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t bits_per_sample = wav_reader->bits_per_sample();
|
||||
|
||||
for (size_t i = 0; i < 240; i++) {
|
||||
wav_reader->data_seek(position + (i * scale));
|
||||
wav_reader->read(&waveform_buffer[i], sizeof(int16_t));
|
||||
if (bits_per_sample == 8) {
|
||||
uint8_t sample;
|
||||
wav_reader->read(&sample, 1);
|
||||
waveform_buffer[i] = (sample - 0x80) * 256;
|
||||
} else {
|
||||
wav_reader->read(&waveform_buffer[i], 2);
|
||||
}
|
||||
}
|
||||
|
||||
waveform.set_dirty();
|
||||
@@ -72,15 +89,33 @@ void ViewWavView::paint(Painter& painter) {
|
||||
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
|
||||
}
|
||||
|
||||
void ViewWavView::on_pos_changed() {
|
||||
position = (field_pos_seconds.value() * wav_reader->sample_rate()) + field_pos_samples.value();
|
||||
void ViewWavView::on_pos_time_changed() {
|
||||
position = (uint64_t)((field_pos_seconds.value() * 1000) + field_pos_milliseconds.value()) * wav_reader->sample_rate() / 1000;
|
||||
field_pos_milliseconds.set_range(0, ((uint32_t)field_pos_seconds.value() == wav_reader->ms_duration() / 1000) ? wav_reader->ms_duration() % 1000 : 999);
|
||||
if (!updating_position) {
|
||||
updating_position = true; // prevent recursion
|
||||
field_pos_samples.set_value(position);
|
||||
updating_position = false;
|
||||
}
|
||||
refresh_waveform();
|
||||
}
|
||||
|
||||
void ViewWavView::on_pos_sample_changed() {
|
||||
position = field_pos_samples.value();
|
||||
if (!updating_position) {
|
||||
updating_position = true; // prevent recursion
|
||||
field_pos_seconds.set_value(field_pos_samples.value() / wav_reader->sample_rate());
|
||||
field_pos_milliseconds.set_value((field_pos_samples.value() * 1000ull / wav_reader->sample_rate()) % 1000);
|
||||
updating_position = false;
|
||||
}
|
||||
refresh_waveform();
|
||||
}
|
||||
|
||||
void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
int16_t sample;
|
||||
uint32_t average;
|
||||
|
||||
wav_file_path = file_path;
|
||||
|
||||
text_filename.set(file_path.filename().string());
|
||||
auto ms_duration = wav_reader->ms_duration();
|
||||
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
|
||||
@@ -88,18 +123,27 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
wav_reader->rewind();
|
||||
|
||||
text_samplerate.set(to_string_dec_uint(wav_reader->sample_rate()) + "Hz");
|
||||
text_bits_per_sample.set(to_string_dec_uint(wav_reader->bits_per_sample(), 2));
|
||||
text_title.set(wav_reader->title());
|
||||
|
||||
// Fill amplitude buffer, world's worst downsampling
|
||||
uint64_t skip = wav_reader->sample_count() / (240 * subsampling_factor);
|
||||
uint8_t bits_per_sample = wav_reader->bits_per_sample();
|
||||
|
||||
for (size_t i = 0; i < 240; i++) {
|
||||
average = 0;
|
||||
|
||||
for (size_t s = 0; s < subsampling_factor; s++) {
|
||||
wav_reader->data_seek(((i * subsampling_factor) + s) * skip);
|
||||
wav_reader->read(&sample, 2);
|
||||
average += (abs(sample) >> 8);
|
||||
if (bits_per_sample == 8) {
|
||||
uint8_t sample;
|
||||
wav_reader->read(&sample, 1);
|
||||
average += sample / 2;
|
||||
} else {
|
||||
int16_t sample;
|
||||
wav_reader->read(&sample, 2);
|
||||
average += (abs(sample) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
amplitude_buffer[i] = average / subsampling_factor;
|
||||
@@ -115,11 +159,109 @@ void ViewWavView::reset_controls() {
|
||||
field_pos_samples.set_value(0);
|
||||
field_cursor_a.set_value(0);
|
||||
field_cursor_b.set_value(0);
|
||||
field_pos_seconds.set_range(0, wav_reader->ms_duration() / 1000);
|
||||
field_pos_milliseconds.set_range(0, (wav_reader->ms_duration() < 1000) ? wav_reader->ms_duration() % 1000 : 999);
|
||||
field_pos_samples.set_range(0, wav_reader->sample_count() - 1);
|
||||
field_scale.set_range(1, std::min(99999ul, wav_reader->sample_count() / 240));
|
||||
}
|
||||
|
||||
bool ViewWavView::is_active() {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void ViewWavView::stop() {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
audio::output::stop();
|
||||
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
ready_signal = false;
|
||||
}
|
||||
|
||||
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
(void)return_code;
|
||||
|
||||
stop();
|
||||
progressbar.set_value(0);
|
||||
|
||||
if (return_code == ReplayThread::READ_ERROR)
|
||||
file_error();
|
||||
|
||||
// Playback complete - now it's safe to update waveform view
|
||||
playback_in_progress = false;
|
||||
if (waveform_update_needed) {
|
||||
waveform_update_needed = false;
|
||||
refresh_waveform();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewWavView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void ViewWavView::file_error() {
|
||||
nav_.display_modal("Error", "File read error.");
|
||||
}
|
||||
|
||||
void ViewWavView::start_playback() {
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
stop();
|
||||
|
||||
if (!reader->open(wav_file_path)) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
playback_in_progress = true;
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
progressbar.set_max(reader->sample_count());
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size, buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Rate of sending progress updates
|
||||
0, // Transmit BW = 0 = not transmitting
|
||||
0, // Gain - unused
|
||||
8, // shift_bits_s16, default 8 bits - unused
|
||||
bits_per_sample, // bits_per_sample
|
||||
0, // tone key disabled
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
transmitter_model.set_sampling_rate(1536000);
|
||||
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
void ViewWavView::on_playback_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
field_pos_samples.set_value(progress);
|
||||
}
|
||||
|
||||
ViewWavView::ViewWavView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
wav_reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
add_children({&labels,
|
||||
@@ -127,26 +269,33 @@ ViewWavView::ViewWavView(
|
||||
&text_samplerate,
|
||||
&text_title,
|
||||
&text_duration,
|
||||
&text_bits_per_sample,
|
||||
&button_open,
|
||||
&button_play,
|
||||
&waveform,
|
||||
&progressbar,
|
||||
&field_pos_seconds,
|
||||
&field_pos_milliseconds,
|
||||
&field_pos_samples,
|
||||
&field_scale,
|
||||
&field_cursor_a,
|
||||
&field_cursor_b,
|
||||
&text_delta});
|
||||
&text_delta,
|
||||
&field_volume});
|
||||
|
||||
reset_controls();
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".WAV");
|
||||
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
|
||||
// Can't show new dialogs in an on_changed handler, so use continuation.
|
||||
nav.set_on_pop([this, &nav, file_path]() {
|
||||
if (!wav_reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Couldn't open file.");
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
|
||||
if ((wav_reader->channels() != 1) || ((wav_reader->bits_per_sample() != 8) && (wav_reader->bits_per_sample() != 16))) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n8 or 16-bit mono files.");
|
||||
return;
|
||||
}
|
||||
load_wav(file_path);
|
||||
@@ -155,14 +304,26 @@ ViewWavView::ViewWavView(
|
||||
};
|
||||
};
|
||||
|
||||
field_volume.set_value(field_volume.value());
|
||||
|
||||
button_play.on_select = [this, &nav](ImageButton&) {
|
||||
if (this->is_active())
|
||||
stop();
|
||||
else
|
||||
start_playback();
|
||||
};
|
||||
|
||||
field_scale.on_change = [this](int32_t value) {
|
||||
update_scale(value);
|
||||
};
|
||||
field_pos_seconds.on_change = [this](int32_t) {
|
||||
on_pos_changed();
|
||||
on_pos_time_changed();
|
||||
};
|
||||
field_pos_milliseconds.on_change = [this](int32_t) {
|
||||
on_pos_time_changed();
|
||||
};
|
||||
field_pos_samples.on_change = [this](int32_t) {
|
||||
on_pos_changed();
|
||||
on_pos_sample_changed();
|
||||
};
|
||||
|
||||
field_cursor_a.on_change = [this](int32_t v) {
|
||||
@@ -179,4 +340,9 @@ void ViewWavView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
ViewWavView::~ViewWavView() {
|
||||
stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -24,12 +25,15 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ViewWavView : public View {
|
||||
public:
|
||||
ViewWavView(NavigationView& nav);
|
||||
~ViewWavView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
@@ -37,15 +41,33 @@ class ViewWavView : public View {
|
||||
std::string title() const override { return "WAV viewer"; };
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"wav_viewer", app_settings::Mode::NO_RF};
|
||||
|
||||
NavigationView& nav_;
|
||||
static constexpr uint32_t subsampling_factor = 8;
|
||||
|
||||
void update_scale(int32_t new_scale);
|
||||
void refresh_waveform();
|
||||
void refresh_measurements();
|
||||
void on_pos_changed();
|
||||
void on_pos_time_changed();
|
||||
void on_pos_sample_changed();
|
||||
void load_wav(std::filesystem::path file_path);
|
||||
void reset_controls();
|
||||
bool is_active();
|
||||
void stop();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void set_ready();
|
||||
void file_error();
|
||||
void start_playback();
|
||||
void on_playback_progress(const uint32_t progress);
|
||||
|
||||
std::filesystem::path wav_file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
const size_t read_size{2048};
|
||||
const size_t buffer_count{3};
|
||||
const uint32_t progress_interval_samples{1536000 / 20};
|
||||
|
||||
std::unique_ptr<WAVFileReader> wav_reader{};
|
||||
|
||||
@@ -54,32 +76,47 @@ class ViewWavView : public View {
|
||||
int32_t scale{1};
|
||||
uint64_t ns_per_pixel{};
|
||||
uint64_t position{};
|
||||
bool updating_position{false};
|
||||
bool playback_in_progress{false};
|
||||
bool waveform_update_needed{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "File:", Color::light_grey()},
|
||||
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()},
|
||||
{{2 * 8, 1 * 16}, "-bit mono", Color::light_grey()},
|
||||
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
|
||||
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
|
||||
{{0 * 8, 11 * 16}, "Position: s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}};
|
||||
{{0 * 8, 12 * 16}, "Position: . s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 13 * 16}, " Sample:", Color::light_grey()},
|
||||
{{0 * 8, 14 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 15 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 16 * 16}, "Delta:", Color::light_grey()},
|
||||
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
|
||||
|
||||
Text text_filename{
|
||||
{5 * 8, 0 * 16, 12 * 8, 16},
|
||||
{5 * 8, 0 * 16, 18 * 8, 16},
|
||||
""};
|
||||
Text text_samplerate{
|
||||
{11 * 8, 1 * 16, 8 * 8, 16},
|
||||
{12 * 8, 1 * 16, 10 * 8, 16},
|
||||
""};
|
||||
Text text_title{
|
||||
{6 * 8, 2 * 16, 18 * 8, 16},
|
||||
{6 * 8, 2 * 16, 17 * 8, 16},
|
||||
""};
|
||||
Text text_duration{
|
||||
{9 * 8, 3 * 16, 18 * 8, 16},
|
||||
{9 * 8, 3 * 16, 20 * 8, 16},
|
||||
""};
|
||||
Text text_bits_per_sample{
|
||||
{0 * 8, 1 * 16, 2 * 8, 16},
|
||||
"16"};
|
||||
Button button_open{
|
||||
{24 * 8, 8, 6 * 8, 2 * 16},
|
||||
"Open"};
|
||||
ImageButton button_play{
|
||||
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 18 * 16}};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 5 * 16, 240, 64},
|
||||
@@ -89,27 +126,40 @@ class ViewWavView : public View {
|
||||
false,
|
||||
Color::white()};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 11 * 16, 30 * 8, 4}};
|
||||
|
||||
NumberField field_pos_seconds{
|
||||
{9 * 8, 11 * 16},
|
||||
{9 * 8, 12 * 16},
|
||||
4,
|
||||
{0, 0},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
NumberField field_pos_milliseconds{
|
||||
{14 * 8, 12 * 16},
|
||||
3,
|
||||
{0, 999},
|
||||
1,
|
||||
' '};
|
||||
'0',
|
||||
true};
|
||||
NumberField field_pos_samples{
|
||||
{14 * 8, 11 * 16},
|
||||
6,
|
||||
{0, 999999},
|
||||
{9 * 8, 13 * 16},
|
||||
9,
|
||||
{0, 0},
|
||||
1,
|
||||
'0'};
|
||||
'0',
|
||||
true};
|
||||
NumberField field_scale{
|
||||
{28 * 8, 11 * 16},
|
||||
2,
|
||||
{1, 40},
|
||||
{25 * 8, 12 * 16},
|
||||
5,
|
||||
{1, 1},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_cursor_a{
|
||||
{9 * 8, 12 * 16},
|
||||
{9 * 8, 14 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -117,7 +167,7 @@ class ViewWavView : public View {
|
||||
true};
|
||||
|
||||
NumberField field_cursor_b{
|
||||
{9 * 8, 13 * 16},
|
||||
{9 * 8, 15 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -125,8 +175,31 @@ class ViewWavView : public View {
|
||||
true};
|
||||
|
||||
Text text_delta{
|
||||
{6 * 8, 14 * 16, 30 * 8, 16},
|
||||
{7 * 8, 16 * 16, 30 * 8, 16},
|
||||
"-"};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
this->handle_replay_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->set_ready();
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_playback_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -211,6 +211,7 @@ void set_audiotx_config(
|
||||
const float deviation_hz,
|
||||
const float audio_gain,
|
||||
uint8_t audio_shift_bits_s16,
|
||||
uint8_t bits_per_sample,
|
||||
const uint32_t tone_key_delta,
|
||||
const bool am_enabled,
|
||||
const bool dsb_enabled,
|
||||
@@ -221,6 +222,7 @@ void set_audiotx_config(
|
||||
deviation_hz,
|
||||
audio_gain,
|
||||
audio_shift_bits_s16,
|
||||
bits_per_sample,
|
||||
tone_key_delta,
|
||||
(float)persistent_memory::tone_mix() / 100.0f,
|
||||
am_enabled,
|
||||
|
||||
@@ -63,7 +63,7 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_fifo_data(const int8_t* data);
|
||||
void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
|
||||
@@ -5759,6 +5759,44 @@ static constexpr Bitmap bitmap_icon_thermometer{
|
||||
{16, 16},
|
||||
bitmap_icon_thermometer_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_brightness_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x80,
|
||||
0x01,
|
||||
0x84,
|
||||
0x21,
|
||||
0x08,
|
||||
0x10,
|
||||
0xC0,
|
||||
0x03,
|
||||
0xE0,
|
||||
0x07,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF6,
|
||||
0x6F,
|
||||
0xF6,
|
||||
0x6F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xE0,
|
||||
0x07,
|
||||
0xC0,
|
||||
0x03,
|
||||
0x08,
|
||||
0x10,
|
||||
0x84,
|
||||
0x21,
|
||||
0x80,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_brightness{
|
||||
{16, 16},
|
||||
bitmap_icon_brightness_data};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
|
||||
@@ -27,14 +27,19 @@
|
||||
void config_mode_blink_until_dfu();
|
||||
|
||||
void config_mode_set() {
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_GUARD_VALUE);
|
||||
uint32_t cms = portapack::persistent_memory::config_mode_storage_direct();
|
||||
if ((cms >= CONFIG_MODE_GUARD_VALUE) && (cms < CONFIG_MODE_LIMIT_VALUE))
|
||||
cms += 1;
|
||||
else
|
||||
cms = CONFIG_MODE_GUARD_VALUE;
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(cms);
|
||||
}
|
||||
|
||||
bool config_mode_should_enter() {
|
||||
if (portapack::persistent_memory::config_disable_config_mode_direct())
|
||||
return false;
|
||||
else
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_GUARD_VALUE;
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
void config_mode_clear() {
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
// number of boot failures before entering config menu mode
|
||||
#define BOOT_FAILURES_BEFORE_CONFIG_MODE 3
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 0xbadb0000
|
||||
#define CONFIG_MODE_LIMIT_VALUE (CONFIG_MODE_GUARD_VALUE + BOOT_FAILURES_BEFORE_CONFIG_MODE - 1)
|
||||
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
|
||||
|
||||
void config_mode_set();
|
||||
bool config_mode_should_enter();
|
||||
void config_mode_clear();
|
||||
|
||||
@@ -25,14 +25,12 @@
|
||||
#include "file.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace std {
|
||||
|
||||
int database::retrieve_mid_record(MidDBRecord* record, std::string search_term) {
|
||||
file_path = "AIS/mids.db";
|
||||
index_item_length = 4;
|
||||
record_length = 32;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
@@ -42,7 +40,7 @@ int database::retrieve_airline_record(AirlinesDBRecord* record, std::string sear
|
||||
index_item_length = 4;
|
||||
record_length = 64;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
@@ -52,12 +50,15 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
index_item_length = 7;
|
||||
record_length = 146;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
||||
auto result = db_file.open(file_path);
|
||||
if (!result.is_valid()) {
|
||||
number_of_records = (db_file.size() / (index_item_length + record_length)); // determine number of records in file
|
||||
@@ -91,5 +92,3 @@ int database::retrieve_record(std::string file_path, int index_item_length, int
|
||||
} else
|
||||
return (DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
} /* namespace std */
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
namespace std {
|
||||
class database {
|
||||
public:
|
||||
#define DATABASE_RECORD_FOUND 0 // record found in database
|
||||
@@ -62,9 +61,9 @@ class database {
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
string file_path = ""; // path inclusing filename
|
||||
int index_item_length = 0; // length of index item
|
||||
int record_length = 0; // length of record
|
||||
std::string file_path = ""; // path inclusing filename
|
||||
int index_item_length = 0; // length of index item
|
||||
int record_length = 0; // length of record
|
||||
|
||||
File db_file{};
|
||||
int number_of_records = 0;
|
||||
@@ -77,6 +76,5 @@ class database {
|
||||
|
||||
int retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -44,6 +44,8 @@ using namespace lpc43xx;
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
static int delayed_error = 0;
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(M4Core_IRQHandler) {
|
||||
@@ -161,6 +163,10 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_RTC_TICK) {
|
||||
// delay error message by 2 seconds to wait for LCD being ready
|
||||
if (portapack::init_error != nullptr && ++delayed_error > 1)
|
||||
draw_guru_meditation(CORTEX_M4, portapack::init_error);
|
||||
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1867,7 +1867,7 @@ static byte menuselect(byte lines) { // Selection (1 line = 16 items)
|
||||
// *** S T A C K
|
||||
|
||||
static void floatstack() {
|
||||
memcpy(ds, &ds[1], (DATASTACKSIZE - 1) * sizeof(double));
|
||||
memmove(ds, &ds[1], (DATASTACKSIZE - 1) * sizeof(double));
|
||||
dp--;
|
||||
}
|
||||
|
||||
|
||||
+16
-6
@@ -3,7 +3,11 @@ set(EXTCPPSRC
|
||||
#pacman
|
||||
external/pacman/main.cpp
|
||||
external/pacman/ui_pacman.cpp
|
||||
|
||||
|
||||
#tetris
|
||||
external/tetris/main.cpp
|
||||
external/tetris/ui_tetris.cpp
|
||||
|
||||
#afsk_rx
|
||||
external/afsk_rx/main.cpp
|
||||
external/afsk_rx/ui_afsk_rx.cpp
|
||||
@@ -19,7 +23,7 @@ set(EXTCPPSRC
|
||||
#blespam
|
||||
external/blespam/main.cpp
|
||||
external/blespam/ui_blespam.cpp
|
||||
|
||||
|
||||
#analogtv
|
||||
external/analogtv/main.cpp
|
||||
external/analogtv/analog_tv_app.cpp
|
||||
@@ -35,13 +39,12 @@ set(EXTCPPSRC
|
||||
#lge
|
||||
external/lge/main.cpp
|
||||
external/lge/lge_app.cpp
|
||||
|
||||
|
||||
#lcr
|
||||
external/lcr/main.cpp
|
||||
external/lcr/ui_lcr.cpp
|
||||
|
||||
|
||||
#lcr
|
||||
|
||||
#jammer
|
||||
external/jammer/main.cpp
|
||||
external/jammer/ui_jammer.cpp
|
||||
|
||||
@@ -59,6 +62,11 @@ set(EXTCPPSRC
|
||||
external/keyfob/main.cpp
|
||||
external/keyfob/ui_keyfob.cpp
|
||||
external/keyfob/ui_keyfob.hpp
|
||||
|
||||
#extsensors
|
||||
external/extsensors/main.cpp
|
||||
external/extsensors/ui_extsensors.cpp
|
||||
external/extsensors/ui_extsensors.hpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -76,4 +84,6 @@ set(EXTAPPLIST
|
||||
gpssim
|
||||
spainter
|
||||
keyfob
|
||||
tetris
|
||||
extsensors
|
||||
)
|
||||
|
||||
+40
-22
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (C) 2023 Bernd Herzog
|
||||
Copyright (C) 2024 Mark Thompson
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,21 +17,28 @@
|
||||
|
||||
MEMORY
|
||||
{
|
||||
/* external apps: regions can't overlap so addresses are corrected after build */
|
||||
ram_external_app_pacman (rwx) : org = 0xEEE90000, len = 32k
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xEEEB0000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xEEEC0000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xEEED0000, len = 32k
|
||||
ram_external_app_analogtv(rwx) : org = 0xEEEE0000, len = 32k
|
||||
ram_external_app_nrf_rx(rwx) : org = 0xEEEF0000, len = 32k
|
||||
ram_external_app_coasterp(rwx) : org = 0xEEF00000, len = 32k
|
||||
ram_external_app_lge(rwx) : org = 0xEEF10000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xEEF20000, len = 32k
|
||||
ram_external_app_jammer(rwx) : org = 0xEEF30000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xEEF40000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xEEF50000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xEEF60000, len = 32k
|
||||
/*
|
||||
* External apps: regions can't overlap so addresses are corrected after build.
|
||||
* Picking uncommon address values for search & replace in binaries (no false positives) - 0xADB00000-0xADEF0000 seems to be good.
|
||||
* Also need to consider processor memory map - reading 0xADxxxxxx generates a fault which may be better than unexpected behavior.
|
||||
* External app address ranges below must match those in python file "external_app_info.py".
|
||||
*/
|
||||
ram_external_app_pacman (rwx) : org = 0xADB00000, len = 32k
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xADB40000, len = 32k
|
||||
ram_external_app_analogtv(rwx) : org = 0xADB50000, len = 32k
|
||||
ram_external_app_nrf_rx(rwx) : org = 0xADB60000, len = 32k
|
||||
ram_external_app_coasterp(rwx) : org = 0xADB70000, len = 32k
|
||||
ram_external_app_lge(rwx) : org = 0xADB80000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xADB90000, len = 32k
|
||||
ram_external_app_jammer(rwx) : org = 0xADBA0000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xADBB0000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xADBC0000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
|
||||
ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
|
||||
ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -59,7 +67,6 @@ SECTIONS
|
||||
*(*ui*external_app*font_viewer*);
|
||||
} > ram_external_app_font_viewer
|
||||
|
||||
|
||||
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_blespam.application_information));
|
||||
@@ -77,40 +84,37 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_nrf_rx.application_information));
|
||||
*(*ui*external_app*nrf_rx*);
|
||||
} > ram_external_app_nrf_rx
|
||||
|
||||
|
||||
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_coasterp.application_information));
|
||||
*(*ui*external_app*coasterp*);
|
||||
} > ram_external_app_coasterp
|
||||
|
||||
|
||||
.external_app_lge : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_lge.application_information));
|
||||
*(*ui*external_app*lge*);
|
||||
} > ram_external_app_lge
|
||||
|
||||
|
||||
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_lcr.application_information));
|
||||
*(*ui*external_app*lcr*);
|
||||
} > ram_external_app_lcr
|
||||
|
||||
|
||||
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_jammer.application_information));
|
||||
*(*ui*external_app*jammer*);
|
||||
} > ram_external_app_jammer
|
||||
|
||||
|
||||
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_gpssim.application_information));
|
||||
*(*ui*external_app*gpssim*);
|
||||
} > ram_external_app_gpssim
|
||||
|
||||
|
||||
.external_app_spainter : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_spainter.application_information));
|
||||
@@ -123,4 +127,18 @@ SECTIONS
|
||||
*(*ui*external_app*keyfob*);
|
||||
} > ram_external_app_keyfob
|
||||
|
||||
.external_app_tetris : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tetris.application_information));
|
||||
*(*ui*external_app*tetris*);
|
||||
} > ram_external_app_tetris
|
||||
|
||||
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_extsensors.application_information));
|
||||
*(*ui*external_app*extsensors*);
|
||||
} > ram_external_app_extsensors
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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_extsensors.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::extsensors {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<ExtSensorsView>();
|
||||
}
|
||||
} // namespace ui::external_app::extsensors
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_extsensors.application_information"), used)) application_information_t _application_information_extsensors = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::extsensors::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "ExtSensor",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x20,
|
||||
0x12,
|
||||
0x48,
|
||||
0x8A,
|
||||
0x51,
|
||||
0xCA,
|
||||
0x53,
|
||||
0xCA,
|
||||
0x53,
|
||||
0x8A,
|
||||
0x51,
|
||||
0x12,
|
||||
0x48,
|
||||
0x84,
|
||||
0x21,
|
||||
0xC0,
|
||||
0x03,
|
||||
0x40,
|
||||
0x02,
|
||||
0x60,
|
||||
0x06,
|
||||
0x20,
|
||||
0x04,
|
||||
0x30,
|
||||
0x0C,
|
||||
0xF0,
|
||||
0x0F,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
|
||||
/*.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,86 @@
|
||||
/*
|
||||
* 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_extsensors.hpp"
|
||||
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::extsensors {
|
||||
|
||||
void ExtSensorsView::focus() {
|
||||
text_info.focus();
|
||||
}
|
||||
|
||||
ExtSensorsView::ExtSensorsView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
&text_info,
|
||||
&text_gps,
|
||||
&text_orientation,
|
||||
&text_envl1,
|
||||
&text_envl2});
|
||||
}
|
||||
|
||||
ExtSensorsView::~ExtSensorsView() {
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_any() {
|
||||
if (has_data == false) {
|
||||
// update text
|
||||
text_info.set("Found ext module."); // todo do an ext module check for type
|
||||
}
|
||||
has_data = true;
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_gps(const GPSPosDataMessage* msg) {
|
||||
on_any();
|
||||
std::string tmp = to_string_decimal(msg->lat, 5);
|
||||
tmp += "; ";
|
||||
tmp += to_string_decimal(msg->lon, 5);
|
||||
text_gps.set(tmp);
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_orientation(const OrientationDataMessage* msg) {
|
||||
on_any();
|
||||
std::string tmp = to_string_dec_uint(msg->angle);
|
||||
tmp += (char)176; // °
|
||||
if (msg->tilt < 400) {
|
||||
tmp += "; T: " + to_string_dec_int(msg->tilt);
|
||||
}
|
||||
text_orientation.set(tmp);
|
||||
}
|
||||
|
||||
void ExtSensorsView::on_environment(const EnvironmentDataMessage* msg) {
|
||||
on_any();
|
||||
std::string tmp = "T: " + to_string_decimal(msg->temperature, 2); // temperature
|
||||
tmp += (char)176; // °
|
||||
tmp += "C";
|
||||
tmp += "; H: " + to_string_decimal(msg->humidity, 1) + "%"; // humidity
|
||||
text_envl1.set(tmp);
|
||||
tmp = "P: " + to_string_decimal(msg->pressure, 1) + " hPa; L:"; // pressure
|
||||
tmp += to_string_dec_int(msg->light) + " LUX"; // light
|
||||
text_envl2.set(tmp);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::extsensors
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// Code from https://github.com/Flipper-XFW/Xtreme-Apps/tree/04c3a60093e2c2378e79498b4505aa8072980a42/ble_spam/protocols
|
||||
// Thanks for the work of the original creators!
|
||||
// Saying thanks in the main view!
|
||||
|
||||
#ifndef __UI_EXTSENSORS_H__
|
||||
#define __UI_EXTSENSORS_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::extsensors {
|
||||
|
||||
class ExtSensorsView : public View {
|
||||
public:
|
||||
ExtSensorsView(NavigationView& nav);
|
||||
~ExtSensorsView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override {
|
||||
return "ExtSensor";
|
||||
};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
bool has_data = false;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "GPS:", Color::light_grey()},
|
||||
{{0 * 8, 5 * 16}, "ORI:", Color::light_grey()},
|
||||
{{0 * 8, 7 * 16}, "ENV:", Color::light_grey()}};
|
||||
|
||||
Text text_info{{0 * 8, 0 * 8, 30 * 8, 16 * 1}, "Connect a compatible module..."};
|
||||
Text text_gps{{5 * 8, 3 * 16, 24 * 8, 16}, "-"};
|
||||
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
|
||||
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
|
||||
Text text_envl2{{1 * 8, 9 * 16, 24 * 8, 16}, "-"};
|
||||
|
||||
void on_any();
|
||||
|
||||
void on_gps(const GPSPosDataMessage* msg);
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
void on_environment(const EnvironmentDataMessage* msg);
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||
this->on_gps(message);
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_orientation{
|
||||
Message::ID::OrientationData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||
this->on_orientation(message);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_environment{
|
||||
Message::ID::EnvironmentData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const EnvironmentDataMessage*>(p);
|
||||
this->on_environment(message);
|
||||
}};
|
||||
};
|
||||
}; // namespace ui::external_app::extsensors
|
||||
|
||||
#endif /*__UI_EXTSENSORS_H__*/
|
||||
+105
-15
@@ -13,8 +13,8 @@ typedef uint8_t byte;
|
||||
typedef uint32_t word;
|
||||
|
||||
byte SPEED = 2;
|
||||
byte MAXLIFES = 5;
|
||||
byte LIFES = START_LIFES;
|
||||
byte MAXLIFES = 20;
|
||||
size_t LIFES = START_LIFES;
|
||||
byte GAMEWIN = 0;
|
||||
byte GAMEOVER = 0;
|
||||
byte DEMO = 1;
|
||||
@@ -25,6 +25,9 @@ byte GAMEPAUSED = 0;
|
||||
|
||||
byte PACMANFALLBACK = 0;
|
||||
|
||||
bool cheat_level = false;
|
||||
bool cheat_lifes = false;
|
||||
|
||||
#include "DrawIndexedMap.h"
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -40,6 +43,16 @@ boolean but_DOWN = false; // 50
|
||||
boolean but_LEFT = false; // 48
|
||||
boolean but_RIGHT = false; // 46
|
||||
|
||||
enum cheat_code_keymap {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT
|
||||
};
|
||||
|
||||
enum cheat_code_keymap cheat_code[] = {UP, DOWN, UP, DOWN, RIGHT, LEFT, RIGHT, LEFT};
|
||||
byte cheat_code_index = 0;
|
||||
|
||||
/******************************************************************************/
|
||||
/* GAME VARIABLES AND DEFINITIONS */
|
||||
/******************************************************************************/
|
||||
@@ -901,14 +914,15 @@ class Playfield {
|
||||
|
||||
void PackmanDied() { // Noooo... PACMAN DIED :(
|
||||
|
||||
if (LIFES <= 0) {
|
||||
if (LIFES <= 0 && !cheat_lifes) {
|
||||
GAMEOVER = 1;
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
DEMO = 1;
|
||||
Init();
|
||||
} else {
|
||||
LIFES--;
|
||||
if (!cheat_lifes)
|
||||
LIFES--;
|
||||
|
||||
_inited = true;
|
||||
_state = ReadyState;
|
||||
@@ -934,8 +948,14 @@ class Playfield {
|
||||
_icons[13 - i] = BONUSICON + i;
|
||||
}
|
||||
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (!cheat_lifes) {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < 14; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1177,8 +1197,13 @@ class Playfield {
|
||||
if (GAMEWIN == 1) {
|
||||
GAMEWIN = 0;
|
||||
} else {
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
if (!cheat_level) {
|
||||
LEVEL = START_LEVEL;
|
||||
}
|
||||
if (!cheat_lifes) {
|
||||
LIFES = START_LIFES;
|
||||
}
|
||||
|
||||
ACTUALBONUS = 0; // actual bonus icon
|
||||
ACTIVEBONUS = 0; // status of bonus
|
||||
|
||||
@@ -1215,8 +1240,14 @@ class Playfield {
|
||||
}
|
||||
|
||||
// SET Lifes icons
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (cheat_lifes) {
|
||||
for (byte i = 0; i < 14; i++) { // cuz 14 lives full fills PP's screen
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1243,21 +1274,80 @@ class Playfield {
|
||||
}
|
||||
|
||||
void Step() {
|
||||
int16_t keys = 0;
|
||||
|
||||
if (GAMEWIN == 1) {
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
LEVEL++;
|
||||
Init();
|
||||
}
|
||||
|
||||
// Start GAME
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) {
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) { // start
|
||||
but_A = false;
|
||||
DEMO = 0;
|
||||
Init();
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // Or PAUSE GAME
|
||||
DEMO = 0;
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // pause
|
||||
but_A = false;
|
||||
GAMEPAUSED = 1;
|
||||
} else if (but_LEFT && DEMO == 1 && GAMEPAUSED == 0) { // -level
|
||||
|
||||
if ((cheat_code[cheat_code_index] == 2) && (cheat_code_index <= 8)) {
|
||||
cheat_code_index++;
|
||||
}
|
||||
|
||||
if (cheat_code_index == 8) {
|
||||
cheat_level = true;
|
||||
if (LEVEL > 1) {
|
||||
LEVEL--;
|
||||
}
|
||||
Init();
|
||||
}
|
||||
|
||||
but_LEFT = false;
|
||||
|
||||
} else if (but_RIGHT && DEMO == 1 && GAMEPAUSED == 0) { // +level
|
||||
|
||||
if ((cheat_code[cheat_code_index] == 3) && (cheat_code_index <= 8)) {
|
||||
cheat_code_index++;
|
||||
}
|
||||
|
||||
if (cheat_code_index == 8) {
|
||||
cheat_level = true;
|
||||
if (LEVEL < 255) {
|
||||
LEVEL++;
|
||||
}
|
||||
Init();
|
||||
}
|
||||
|
||||
but_RIGHT = false;
|
||||
|
||||
} else if (but_UP && DEMO == 1 && GAMEPAUSED == 0) { // full of lifes
|
||||
|
||||
if ((cheat_code[cheat_code_index] == 0) && (cheat_code_index <= 8)) {
|
||||
cheat_code_index++;
|
||||
}
|
||||
|
||||
if (cheat_code_index == 8) {
|
||||
cheat_lifes = true;
|
||||
Init();
|
||||
}
|
||||
|
||||
but_UP = false;
|
||||
|
||||
} else if (but_DOWN && DEMO == 1 && GAMEPAUSED == 0) { // reset
|
||||
|
||||
if ((cheat_code[cheat_code_index] == 1) && (cheat_code_index <= 8)) {
|
||||
cheat_code_index++;
|
||||
}
|
||||
|
||||
if (cheat_code_index == 8) {
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
but_DOWN = false;
|
||||
LIFES = START_LIFES;
|
||||
cheat_code_index = 0;
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
if (GAMEPAUSED && but_A && DEMO == 0) {
|
||||
|
||||
+22
-7
@@ -13,8 +13,6 @@ namespace ui::external_app::pacman {
|
||||
#include "playfield.hpp"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
Playfield _game;
|
||||
|
||||
PacmanView::PacmanView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&dummy});
|
||||
@@ -26,19 +24,36 @@ void PacmanView::focus() {
|
||||
|
||||
void PacmanView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
static Playfield _game;
|
||||
static bool wait_for_button_release{false};
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
_game.Init();
|
||||
}
|
||||
|
||||
auto switches_raw = swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
|
||||
but_RIGHT = (switches_debounced & 0x01) == 0x01;
|
||||
but_LEFT = (switches_debounced & 0x02) == 0x02;
|
||||
but_DOWN = (switches_debounced & 0x04) == 0x04;
|
||||
but_UP = (switches_debounced & 0x08) == 0x08;
|
||||
but_A = (switches_debounced & 0x10) == 0x10;
|
||||
// For the Select (Start/Pause) button, wait for release to avoid repeat
|
||||
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
|
||||
if (wait_for_button_release) {
|
||||
if ((switches_debounced & buttons_to_wait_for) == 0)
|
||||
wait_for_button_release = false;
|
||||
switches_debounced &= ~buttons_to_wait_for;
|
||||
} else {
|
||||
if (switches_debounced & buttons_to_wait_for)
|
||||
wait_for_button_release = true;
|
||||
}
|
||||
|
||||
// For the directional buttons, use the raw inputs for fastest response time
|
||||
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
|
||||
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
|
||||
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
|
||||
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
|
||||
|
||||
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
|
||||
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
|
||||
|
||||
_game.Step();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// dummy include file to avoid changing original source
|
||||
|
||||
#ifndef __UI_Arial12x12_H__
|
||||
#define __UI_Arial12x12_H__
|
||||
|
||||
#define Arial12x12 (0)
|
||||
|
||||
#endif /*__UI_Arial12x12_H__*/
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// "HAL" display layer for Tetris code to run on PortaPack without its original ILI9341 functions
|
||||
|
||||
#ifndef __UI_SPI_TFT_ILI9341_H__
|
||||
#define __UI_SPI_TFT_ILI9341_H__
|
||||
|
||||
ui::Painter painter;
|
||||
|
||||
static int x_pos{0};
|
||||
static int y_pos{0};
|
||||
static int fg_color;
|
||||
static int bg_color;
|
||||
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
// pp_colors must be in same order as enums above
|
||||
static const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
// NB: ELIMINATED SPI_TFT_ILI9341 DISPLAY CLASS DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS
|
||||
|
||||
static void claim(__FILE* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
};
|
||||
|
||||
static void background(int color) {
|
||||
bg_color = color;
|
||||
};
|
||||
|
||||
static void foreground(int color) {
|
||||
fg_color = color;
|
||||
};
|
||||
|
||||
static void locate(int x, int y) {
|
||||
x_pos = x;
|
||||
y_pos = y;
|
||||
};
|
||||
|
||||
static void set_orientation(int x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void set_font(unsigned char* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
static void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
static void printf(std::string str) {
|
||||
auto style = (fg_color == White) ? ui::Styles::white : ui::Styles::bg_white;
|
||||
painter.draw_string({x_pos, y_pos - 1}, style, str);
|
||||
};
|
||||
|
||||
static void printf(std::string str, int v) {
|
||||
if (str.find_first_of("%") != std::string::npos) {
|
||||
str.resize(str.find_first_of("%")); // remove %d from end of string
|
||||
}
|
||||
printf(str + to_string_dec_uint(v));
|
||||
};
|
||||
|
||||
#endif /*__UI_SPI_TFT_ILI9341_H__*/
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_tetris.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<TetrisView>();
|
||||
}
|
||||
} // namespace ui::external_app::tetris
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_tetris.application_information"), used)) application_information_t _application_information_tetris = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
|
||||
/*.externalAppEntry = */ ui::external_app::tetris::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Tetris",
|
||||
/*.bitmap_data = */ {
|
||||
0xF8,
|
||||
0xFF,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0xF8,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x08,
|
||||
0x80,
|
||||
0x08,
|
||||
0x9F,
|
||||
0x08,
|
||||
0x91,
|
||||
0x0F,
|
||||
0x11,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0xFF,
|
||||
0xF1,
|
||||
0x11,
|
||||
0x91,
|
||||
0x11,
|
||||
0x91,
|
||||
0x11,
|
||||
0x91,
|
||||
0xFF,
|
||||
0xF1,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_noop */ {'\0', '\0', '\0', '\0'}, // optional
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// "HAL" layer for Tetris code to run on PortaPack without its original mbed OS
|
||||
// (the dream here was to avoid modifying the original code)
|
||||
|
||||
#ifndef __UI_mbed_H__
|
||||
#define __UI_mbed_H__
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
#define wait_us(x) (void)0
|
||||
#define wait(x) chThdSleepMilliseconds(x * 1000)
|
||||
#define PullUp 1
|
||||
|
||||
enum {
|
||||
dp0,
|
||||
dp1,
|
||||
dp2,
|
||||
dp3,
|
||||
dp4,
|
||||
dp5,
|
||||
dp6,
|
||||
dp7,
|
||||
dp8,
|
||||
dp9,
|
||||
dp10,
|
||||
dp11,
|
||||
dp12,
|
||||
dp13,
|
||||
dp14,
|
||||
dp15,
|
||||
dp16,
|
||||
dp17,
|
||||
dp18,
|
||||
dp19,
|
||||
dp20,
|
||||
dp21,
|
||||
dp22,
|
||||
dp23,
|
||||
dp24,
|
||||
dp25,
|
||||
};
|
||||
|
||||
static bool but_RIGHT;
|
||||
static bool but_LEFT;
|
||||
static bool but_UP;
|
||||
static bool but_DOWN;
|
||||
static bool but_SELECT;
|
||||
|
||||
//
|
||||
// AnalogIn Class -- DID NOT WORK DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS -- hacked original code module instead
|
||||
//
|
||||
// dp9 = joystick rotate button --> select button
|
||||
// dp10 = joystick y --> up & down buttons
|
||||
// dp11 = joystick x --> left & right buttons
|
||||
// dp13 = random number generator
|
||||
//
|
||||
//
|
||||
// class AnalogIn {
|
||||
// public:
|
||||
// AnalogIn(uint32_t analog_input) {
|
||||
// // FIXME - THIS CODE NEVER GETS EXECUTED!
|
||||
// analog_input_ = analog_input;
|
||||
// };
|
||||
//
|
||||
// // Tetris code only uses this function for dp13 - supposed to be a random number
|
||||
// uint16_t read_u16() {
|
||||
// return std::rand();
|
||||
// };
|
||||
//
|
||||
// // Tetris code uses read() function for direction buttons only
|
||||
// float read() {
|
||||
// float retval = 0.5;
|
||||
// switch (analog_input_) {
|
||||
// case dp11:
|
||||
// if (but_LEFT)
|
||||
// retval = 0.0;
|
||||
// else if (but_RIGHT)
|
||||
// retval = 1.0;
|
||||
// break;
|
||||
//
|
||||
// case dp10:
|
||||
// if (but_UP)
|
||||
// retval = 0.0;
|
||||
// else if (but_DOWN)
|
||||
// retval = 1.0;
|
||||
// break;
|
||||
// }
|
||||
// return retval;
|
||||
// };
|
||||
//
|
||||
// operator float() {
|
||||
// return read();
|
||||
// };
|
||||
//
|
||||
// private:
|
||||
// uint32_t analog_input_{INT_MAX};
|
||||
// };
|
||||
|
||||
//
|
||||
// Timer Class
|
||||
// (Timer object was used for unneeded button debouncing, so just returning 1000ms to indicate we've waited long enough)
|
||||
//
|
||||
class Timer {
|
||||
public:
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
Timer() { (void)0; };
|
||||
void reset() { (void)0; };
|
||||
void start() { (void)0; }
|
||||
uint32_t read_ms() { return 1000; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
//
|
||||
// Ticker Class
|
||||
// (Ticker is timed callback, used for checking "joystick" directional switches and when time to move piece down a row)
|
||||
//
|
||||
// NB: Only one callback is supported per Ticker class instantiation
|
||||
static Callback fall_timer_callback;
|
||||
static uint32_t fall_timer_timeout;
|
||||
static uint32_t fall_timer_counter;
|
||||
|
||||
static Callback dir_button_callback;
|
||||
|
||||
static void check_fall_timer() {
|
||||
if (fall_timer_callback) {
|
||||
if (++fall_timer_counter >= fall_timer_timeout) {
|
||||
fall_timer_counter = 0;
|
||||
fall_timer_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
Ticker() { (void)0; };
|
||||
|
||||
void attach(Callback func, double delay_sec) {
|
||||
// 0.3 sec is requested only for button check -- kludge to use on_key callback for this one instead of timer
|
||||
if (delay_sec == 0.3) {
|
||||
dir_button_callback = func;
|
||||
} else {
|
||||
fall_timer_callback = func;
|
||||
fall_timer_timeout = delay_sec * 60; // timer interrupts at 60 Hz
|
||||
}
|
||||
}
|
||||
|
||||
void detach() {
|
||||
// shouldn't detach both, but don't know how to tell which object is which
|
||||
dir_button_callback = nullptr;
|
||||
fall_timer_callback = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
//
|
||||
// InterruptIn Class
|
||||
// (just used for the Select button)
|
||||
//
|
||||
static Callback sel_button_callback;
|
||||
|
||||
static bool check_encoder(const EncoderEvent delta) {
|
||||
(void)delta;
|
||||
// TODO: consider adding ability to rotate Tetronimo via encoder too
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_key(const KeyEvent key) {
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
but_RIGHT = (switches_debounced & 0x01) != 0;
|
||||
but_LEFT = (switches_debounced & 0x02) != 0;
|
||||
but_DOWN = (switches_debounced & 0x04) != 0;
|
||||
but_UP = (switches_debounced & 0x08) != 0;
|
||||
but_SELECT = (switches_debounced & 0x10) != 0;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
if (sel_button_callback)
|
||||
sel_button_callback();
|
||||
} else {
|
||||
if (dir_button_callback)
|
||||
dir_button_callback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class InterruptIn {
|
||||
public:
|
||||
InterruptIn(int reg) {
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
(void)reg;
|
||||
};
|
||||
void fall(Callback func) { sel_button_callback = func; };
|
||||
void mode(int v) { (void)v; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif /*__UI_mbed_H__*/
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
// Projekat Tetris
|
||||
// by Vrnjak Lamija & Selimović Denis
|
||||
// Elektrotehnički fakultet Sarajevo
|
||||
|
||||
// clang-format off
|
||||
|
||||
//////// HACKED FOR PORTAPACK -- CHANGES HIGHLIGHTED
|
||||
int main();
|
||||
void pause_game();
|
||||
void Initialize(unsigned char c);
|
||||
void DeleteFigure();
|
||||
void DrawFigure();
|
||||
bool InCollisionDown(char delta);
|
||||
bool InCollisionLeft();
|
||||
bool InCollisionRight();
|
||||
//////// PORTAPACK
|
||||
|
||||
#include "mbed.h"
|
||||
#include "SPI_TFT_ILI9341.h"
|
||||
#include "Arial12x12.h"
|
||||
|
||||
#define dp23 P0_0
|
||||
|
||||
//////// PORTAPACK - DISABLED MOST CLASSES DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS:
|
||||
//deklaracija display-a
|
||||
//SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT");
|
||||
//
|
||||
//analogni ulazi za joystick
|
||||
// AnalogIn VRx(dp11);
|
||||
// AnalogIn VRy(dp10);
|
||||
//
|
||||
// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti
|
||||
//////// PORTAPACK
|
||||
|
||||
//taster na joysticku za rotaciju
|
||||
InterruptIn taster(dp9);
|
||||
|
||||
//ticker za spustanje figure
|
||||
//timer za debouncing tastera na joysticku
|
||||
Ticker game, joystick;
|
||||
Timer debounceTaster;
|
||||
|
||||
unsigned char level = 0; //mora biti tipa usigned char jer inače se može desiti da level bude manji od 0, a i da ne trošimo memoriju
|
||||
const float delays[4] = {1.2, 0.7, 0.4, 0.25}; //svakih koliko se spusti jedan red, ovo provjeriti da li je presporo ili prebrzo, ovisi o levelu
|
||||
|
||||
//////// PORTAPACK - DELETED UNNEEDED JOYSTICK HYSTERESIS VARIABLES
|
||||
//char leftBoundary = 1, rightBoundary = 5, downBoundary = 1, upBoundary = 5;// sada je ovo tipa char
|
||||
//////// PORTAPACK
|
||||
|
||||
unsigned int score = 0; //stavio sam ovo unsigned int za veći opseg, mada je jako teško da se i int premaši, ali nmvz
|
||||
bool firstTime = true; //ako je prvi put, figura se crta u Tickeru
|
||||
bool gameStarted = false;
|
||||
unsigned char nextFigure = 1; //ovo je sad globalna varijabla, da bi mogli unaprijed generisati sljedeću figuru radi prikaza
|
||||
|
||||
//white - no figure
|
||||
//I - BLUE
|
||||
//O - YELLOW
|
||||
//T - PURPLE
|
||||
//S - GREEN
|
||||
//Z - RED
|
||||
//J - MAROON
|
||||
//L - ORANGE
|
||||
const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
|
||||
const short DIMENSION = 16;
|
||||
const short DIMENSION_NEXT = 12;
|
||||
short board[20][10] = {0}; //matrica boja, 0 - 7 indeksi boja
|
||||
|
||||
short figuresX[7][4] = {{0,0,0,0}, {0,0,1,1}, {0,1,1,1}, {1,1,0,0}, {0,1,0,1}, {1,1,1,0}, {1,1,1,0}};
|
||||
short figuresY[7][4] = {{0,1,2,3}, {1,0,0,1}, {1,1,2,0}, {0,1,1,2}, {0,1,1,2}, {2,1,0,0}, {0,1,2,2}};
|
||||
|
||||
unsigned int GenerateRandomSeed() {
|
||||
//////// PORTAPACK - USE RTC FOR SEED
|
||||
return LPC_RTC->CTIME0;
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
void Init() {
|
||||
//ovo su zajedničke osobine za sve prikaze na display-u
|
||||
//nikad se ne mijenjaju i pozvat ćemo je jednom prije petlje
|
||||
claim(stdout);
|
||||
set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat
|
||||
set_font((unsigned char*) Arial12x12);
|
||||
}
|
||||
|
||||
|
||||
void ShowScore() {
|
||||
//pomocna funkcija za prikazivanje score-a
|
||||
fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score
|
||||
locate(200, 35); //valjda je na sredini pravougaonika
|
||||
printf("%d", score);
|
||||
}
|
||||
|
||||
void ShowNextFigure() {
|
||||
//prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure
|
||||
fillrect(165, 70, 235, 120, White);
|
||||
int upperLeftX = 176, upperLeftY = 83;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
|
||||
fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
|
||||
rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
//funkcija za crtanje cursora za odabir levela
|
||||
void DrawCursor(int color, unsigned char lev) {
|
||||
fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
|
||||
}
|
||||
|
||||
// PORTAPACK - ADDED EXTRA LEVEL:
|
||||
void ShowLevelMenu() {
|
||||
//ovdje inicijalizujemo display
|
||||
cls(); // brišemo prethodno
|
||||
background(Black);
|
||||
foreground(White);
|
||||
locate(80, 50);
|
||||
printf("LEVEL 1");
|
||||
locate(80, 120);
|
||||
printf("LEVEL 2");
|
||||
locate(80, 190);
|
||||
printf("LEVEL 3");
|
||||
locate(80, 260);
|
||||
printf("LEVEL 4");
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
//////// PORTAPACK - USE BUTTONS VS JOYSTICK:
|
||||
void ReadJoystickForLevel(){
|
||||
unsigned char old = level;
|
||||
if(but_UP){
|
||||
(level == 0) ? level = 3 : level--;
|
||||
}
|
||||
else if(but_DOWN){
|
||||
//ne radi ona prethodna varijanta jer % vraća i negastivni rezultat
|
||||
//to što ne koristimo unsigned tip ne pomaže jer će doći do overflow-a
|
||||
level = (level + 1) % 4;
|
||||
}
|
||||
DrawCursor(Black, old); //na prethodni level popunimo bojom pozadine
|
||||
DrawCursor(White, level); //na novi level popunimo bijelom bojom - pozadina je crna
|
||||
//koristio sam fillrect, jer njega svakako moramo koristiti, jer možda budemo morali da brišemo fillcircle iz biblioteke
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
void EndPlay() {
|
||||
joystick.detach();
|
||||
score = 0;
|
||||
firstTime = true;
|
||||
gameStarted = false;
|
||||
for(int i = 0; i < 20; i++) {
|
||||
for(int j = 0; j < 10; j++) {
|
||||
board[i][j] = 0;
|
||||
}
|
||||
}
|
||||
//////// PORTAPACK - FIX TO REINITIALIZE SCREEN
|
||||
// ShowLevelMenu();
|
||||
// joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
main();
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
void StartGame()
|
||||
{
|
||||
cls(); // brišemo ShowLevelMenu
|
||||
background(White);
|
||||
foreground(Black);
|
||||
fillrect(0, 0, 160, 320, White);
|
||||
fillrect(160, 0, 240, 320, Black); //dio za prikazivanje rezultata će biti crni pravougaonik, a tabla je bijeli
|
||||
ShowScore();
|
||||
}
|
||||
|
||||
void copyCoordinates(short X[], short Y[], unsigned char index)
|
||||
{
|
||||
//umjesto one prošle metode za kopiranje, ova prima index, čisto da nam olakša život
|
||||
for(int i = 0; i < 4; i++) {
|
||||
X[i] = figuresX[index][i];
|
||||
Y[i] = figuresY[index][i];
|
||||
}
|
||||
}
|
||||
|
||||
bool BottomEdge(int x){
|
||||
return x > 19;
|
||||
}
|
||||
|
||||
bool LeftEdge(int y){
|
||||
return y < 0;
|
||||
}
|
||||
|
||||
bool RightEdge(int y){
|
||||
return y > 9;
|
||||
}
|
||||
|
||||
bool OutOfBounds(int y, int x){
|
||||
return y < 0 || y > 9 || x > 19;
|
||||
}
|
||||
|
||||
void PutBorders(short x, short y) {
|
||||
for(int i = x - 1; i <= x + 1; i++) {
|
||||
for(int j = y - 1; j <= y + 1; j++) {
|
||||
if(i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
|
||||
rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////// PORTAPACK - ELIMINATED CLASSES DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS:
|
||||
//class Tetromino{
|
||||
//private:
|
||||
short X[4];
|
||||
short Y[4];
|
||||
short boardX, boardY;
|
||||
unsigned char colorIndex;//dodao sam colorIndex zasad, jer nema drugog načina da popunimo matricu sa indeksima boja
|
||||
//ovo je najbezbolnija varijanta što se memorije tiče
|
||||
//public:
|
||||
// Tetromino(){
|
||||
// unsigned char r = rand() % 7 + 1;
|
||||
// Initialize(r);
|
||||
// }
|
||||
|
||||
void Tetromino(unsigned char c) {
|
||||
Initialize(c);
|
||||
}
|
||||
|
||||
void Initialize(unsigned char c) {
|
||||
colorIndex = c;
|
||||
boardX = 0;
|
||||
boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display
|
||||
copyCoordinates(X, Y, c - 1);
|
||||
}
|
||||
|
||||
void Rotate(){
|
||||
short pivotX = X[1], pivotY = Y[1];
|
||||
//prvi elemnti u matrici su pivoti oko koje rotiramo
|
||||
|
||||
short newX[4]; //pozicije blokova nakon rotiranja ako sve bude ok
|
||||
short newY[4];
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
short tmp = X[i], tmp2 = Y[i];
|
||||
newX[i] = pivotX + pivotY - tmp2;
|
||||
newY[i] = tmp + pivotX - pivotY;
|
||||
|
||||
if(OutOfBounds(boardY + newY[i], boardX + newX[i]) || board[boardX + newX[i]][boardY + newY[i]] != 0) return;
|
||||
}
|
||||
DeleteFigure();
|
||||
for(int i = 0; i < 4; i++){
|
||||
X[i] = newX[i];
|
||||
Y[i] = newY[i];
|
||||
}
|
||||
DrawFigure();
|
||||
}
|
||||
|
||||
void DrawFigure() {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
//display je deklarisani display logy iz biblioteke one
|
||||
//računamo gornji lijevi pixel po x i y
|
||||
//donji desni dobijemo kad dodamo DIMENZIJU
|
||||
//stavio sam 16 za početak, možemo se opet skontati na labu
|
||||
//ovo pretpostavlja da nema margina, mogu se lagano dodati uz neku konstantu kao offset
|
||||
int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
|
||||
//ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa
|
||||
rect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteFigure() {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
//ista logika kao u DrawFigure, samo popunjavamo sve blokove sa bijelim pravougaonicima
|
||||
short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, White);
|
||||
PutBorders(upperLeftY, upperLeftX);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAttached() {
|
||||
//metoda se poziva kad figura prestanje da se krece
|
||||
//popunimo matricu indeksom boje
|
||||
for(int i = 0; i < 4; i++) {
|
||||
board[boardX + X[i]][boardY + Y[i]] = colorIndex;
|
||||
//btw board bi mogao biti niz tipa unsigned char, ali to ćemo vidjet kasnije
|
||||
}
|
||||
}
|
||||
|
||||
bool MoveDown(char delta = 1){
|
||||
if(!InCollisionDown(delta)){
|
||||
DeleteFigure();
|
||||
boardX+=delta;
|
||||
DrawFigure();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MoveLeft(){
|
||||
if(!InCollisionLeft()){
|
||||
DeleteFigure();
|
||||
boardY--;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void MoveRight(){
|
||||
if(!InCollisionRight()){
|
||||
DeleteFigure();
|
||||
boardY++;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void SoftDrop() {
|
||||
//ovo je funkcija za soft drop
|
||||
//obrisemo figuru, postavimo novu poziciju dva reda ispod, nacrtamo figuru
|
||||
DeleteFigure();
|
||||
MoveDown(2);
|
||||
DrawFigure();
|
||||
//treba još vidjeti koje izmjene u tickeru trebaju
|
||||
score += 2 * (level +1); //prema igrici koju smo igrali, dobije se 14 poena kad se uradi hard drop
|
||||
ShowScore();
|
||||
}
|
||||
|
||||
|
||||
bool InCollisionDown(char delta = 1){
|
||||
int newX, newY; //da bi bilo citljivije
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i] + delta;
|
||||
newY = boardY + Y[i];
|
||||
|
||||
if(BottomEdge(newX) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
//jedna figura je u koliziji ako
|
||||
//pozicija na koju zelimo da pomjerimo bilo koji blok dotakne dno ili lijevu ili desnu ivicu ekrana
|
||||
//ili ako je pozicija na koju zelimo da pomjerimo bilo koji od blokova vec zauzeta a da nije dio figure prije pomijeranja
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InCollisionLeft(){
|
||||
int newX, newY;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] - 1;
|
||||
|
||||
if(LeftEdge(newY) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool InCollisionRight(){
|
||||
int newX, newY;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] + 1;
|
||||
|
||||
if(RightEdge(newY) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// };
|
||||
|
||||
// Tetromino currentTetromino;
|
||||
|
||||
//////// PORTAPACK - USE BUTTONS VS JOYSTICK, AND ADDED PAUSE FEATURE:
|
||||
void ReadJoystickForFigure() {
|
||||
if(but_LEFT) {
|
||||
MoveLeft();
|
||||
}
|
||||
else if(but_RIGHT) {
|
||||
MoveRight();
|
||||
}
|
||||
else if(but_UP) {
|
||||
pause_game();
|
||||
}
|
||||
else if(but_DOWN) {
|
||||
SoftDrop();
|
||||
}
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
void CheckLines(short &firstLine, short &numberOfLines)
|
||||
{
|
||||
//vraća preko reference prvu liniju koja je popunjena do kraja, kao i takvih linija
|
||||
firstLine = -1; //postavljen na -1 jer ako nema linija koje treba brisati ispod u UpdateBoard neće se ući u petlju
|
||||
numberOfLines = 0;
|
||||
for(int i = 19; i >= 0; i--) {
|
||||
short temp = 0;
|
||||
for(int j = 0; j < 10; j++) {
|
||||
if(board[i][j] == 0) {
|
||||
if(numberOfLines > 0) return;
|
||||
break;
|
||||
}//ako je makar jedna bijela, prekida se brojanje
|
||||
temp++;
|
||||
}
|
||||
if(temp == 10) { //ako je temo došao do 10, niti jedna bijela - puna linija
|
||||
numberOfLines++;
|
||||
if(firstLine == -1) firstLine = i; //ovo mijenjamo samo prvi put
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int UpdateScore (short numOfLines){
|
||||
unsigned int newIncrement = 0;
|
||||
switch(numOfLines) {
|
||||
case 1 : newIncrement = 40; break;
|
||||
case 2 : newIncrement = 100; break;
|
||||
case 3 : newIncrement = 300; break;
|
||||
case 4 : newIncrement = 1200; break;
|
||||
default : newIncrement = 0; break; //update funkcije za score, još sam vratio ovo na 0
|
||||
}
|
||||
return newIncrement * (level + 1);
|
||||
}
|
||||
|
||||
void UpdateBoard()
|
||||
{
|
||||
short firstLine, numberOfLines;
|
||||
//pozivamo funkciju
|
||||
do {
|
||||
CheckLines(firstLine, numberOfLines);
|
||||
for(int i = firstLine; i >= numberOfLines; i--) {
|
||||
for(int j = 0; j < 10; j++) {
|
||||
board[i][j] = board[i - numberOfLines][j];
|
||||
board[i - numberOfLines][j] = 0;
|
||||
short tmp = i - numberOfLines;
|
||||
fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok
|
||||
fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White);
|
||||
if(board[i][j] != 0)
|
||||
rect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , Black);
|
||||
}
|
||||
}
|
||||
score += UpdateScore(numberOfLines);
|
||||
}
|
||||
while(numberOfLines != 0); //ovdje se mijenja globalna varijabla score
|
||||
}
|
||||
|
||||
bool IsOver() {
|
||||
for(int i = 0; i < 10; i++) {
|
||||
if(board[0][i] != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowGameOverScreen() {
|
||||
//////// PORTAPACK - SKIP CLS
|
||||
// cls();
|
||||
// background(Black);
|
||||
// foreground(White);
|
||||
background(White);
|
||||
foreground(Black);
|
||||
//////// PORTAPACK
|
||||
locate(60, 120);
|
||||
printf("GAME OVER");
|
||||
locate(40, 150);
|
||||
printf("YOUR SCORE IS %d", score);
|
||||
wait(5); //ovaj prikaz traje 3s (možemo mijenjati) a nakon toga se ponovo prikazuje meni sa levelima
|
||||
}
|
||||
|
||||
void InitGame() {
|
||||
if(firstTime) {
|
||||
Tetromino(rand() % 7 + 1);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayGame(){
|
||||
InitGame();
|
||||
if(!MoveDown()){
|
||||
OnAttached();
|
||||
UpdateBoard();
|
||||
ShowScore();
|
||||
|
||||
Tetromino(nextFigure);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
if(IsOver()) {
|
||||
//ako je igra završena brišemo sve sa displey-a, prikazujemo poruku i score
|
||||
//takođe moramo dettach-at ticker
|
||||
game.detach();
|
||||
ShowGameOverScreen(); //prikaz da je kraj igre, ima wait od 3s
|
||||
EndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OnTasterPressed(){
|
||||
if(debounceTaster.read_ms() > 200) {
|
||||
if(gameStarted){
|
||||
Rotate();
|
||||
}
|
||||
else{
|
||||
joystick.detach();
|
||||
gameStarted = true;
|
||||
StartGame(); //pocinje igra, prikazuje se tabla
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
|
||||
}
|
||||
debounceTaster.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SetTaster() {
|
||||
taster.mode(PullUp); //mora se aktivirati pull up otpornik na tasteru joystick-a
|
||||
taster.fall(&OnTasterPressed);
|
||||
debounceTaster.reset();
|
||||
debounceTaster.start();
|
||||
}
|
||||
|
||||
int main() {
|
||||
srand(GenerateRandomSeed());
|
||||
Init();
|
||||
ShowLevelMenu();
|
||||
joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
SetTaster();
|
||||
|
||||
//////// PORTAPACK: CAN'T HANG HERE IN MAYHEM OR WE WON'T GET ANY CALLBACKS
|
||||
// while(1);
|
||||
return 0;
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
//////// PORTAPACK - ADDED PAUSE FEATURE:
|
||||
void pause_game() {
|
||||
game.detach();
|
||||
joystick.detach();
|
||||
locate(180, 200);
|
||||
printf("PAUSED");
|
||||
while ((get_switches_state().to_ulong() & 0x10) == 0); // wait for SELECT button to resume
|
||||
printf(" ");
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
|
||||
};
|
||||
//////// PORTAPACK
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_tetris.hpp"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
// external code, so ignore warnings
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
#include "tetris.cpp"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
TetrisView::TetrisView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&dummy});
|
||||
}
|
||||
|
||||
void TetrisView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
||||
void TetrisView::frame_sync() {
|
||||
check_fall_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool TetrisView::on_encoder(const EncoderEvent delta) {
|
||||
return check_encoder(delta);
|
||||
}
|
||||
|
||||
bool TetrisView::on_key(const KeyEvent key) {
|
||||
return check_key(key);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::tetris
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_TETRIS_H__
|
||||
#define __UI_TETRIS_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "limits.h"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
|
||||
class TetrisView : public View {
|
||||
public:
|
||||
TetrisView(NavigationView& nav);
|
||||
|
||||
std::string title() const override { return "Tetris"; };
|
||||
|
||||
void focus() override { dummy.focus(); };
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::tetris
|
||||
|
||||
#endif /*__UI_TETRIS_H__*/
|
||||
@@ -376,7 +376,7 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
} else if (key == "c") {
|
||||
entry.tone = parse_tone_key(value);
|
||||
} else if (key == "d") {
|
||||
entry.description = trim(value);
|
||||
entry.description = trim(value).substr(0, freqman_max_desc_size);
|
||||
} else if (key == "f") {
|
||||
entry.type = freqman_type::Single;
|
||||
parse_int(value, entry.frequency_a);
|
||||
@@ -492,7 +492,7 @@ freqman_entry FreqmanDB::operator[](Index index) const {
|
||||
return entry;
|
||||
else if (read_raw_) {
|
||||
entry.type = freqman_type::Raw;
|
||||
entry.description = trim(*line_text);
|
||||
entry.description = trim(*line_text).substr(0, freqman_max_desc_size);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,9 @@ std::string freqman_entry_get_step_string_short(freqman_index_t step);
|
||||
* ensure app memory stability. */
|
||||
constexpr size_t freqman_default_max_entries = 150;
|
||||
|
||||
/* Limiting description to 30 as specified by the format */
|
||||
constexpr size_t freqman_max_desc_size = 30;
|
||||
|
||||
struct freqman_load_options {
|
||||
/* Loads all entries when set to 0. */
|
||||
size_t max_entries{freqman_default_max_entries};
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
uint8_t Debounce::state() {
|
||||
bool v = state_to_report_;
|
||||
uint8_t v = state_to_report_;
|
||||
simulated_pulse_ = false;
|
||||
return v;
|
||||
}
|
||||
@@ -149,3 +152,40 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::rotation_rate() {
|
||||
return last_rotation_rate_;
|
||||
}
|
||||
|
||||
// Returns TRUE if encoder position phase bits changed (after debouncing)
|
||||
bool EncoderDebounce::feed(const uint8_t phase_bits) {
|
||||
history_ = (history_ << 2) | phase_bits;
|
||||
|
||||
// If both inputs have been stable for the last 4 ticks, history_ should equal 0x00, 0x55, 0xAA, or 0xFF.
|
||||
uint8_t expected_stable_history = phase_bits * 0b01010101;
|
||||
|
||||
// But, checking for equal seems to cause issues with at least 1 user's encoder, so we're treating the input
|
||||
// as "stable" if at least ONE input bit is consistent for 4 ticks...
|
||||
uint8_t diff = (history_ ^ expected_stable_history);
|
||||
if (((diff & 0b01010101) == 0) || ((diff & 0b10101010) == 0)) {
|
||||
// Has the debounced input value changed?
|
||||
if (state_ != phase_bits) {
|
||||
state_ = phase_bits;
|
||||
|
||||
// Rate multiplier is for larger delta increments when dial is rotated rapidly.
|
||||
last_rotation_rate_ = rotation_rate_downcounter_;
|
||||
rotation_rate_downcounter_ = portapack::persistent_memory::encoder_rate_multiplier();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unstable input, or no change.
|
||||
// Decrement rotation rate detector once per timer tick.
|
||||
if (rotation_rate_downcounter_ > 1)
|
||||
rotation_rate_downcounter_--;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -64,4 +65,21 @@ class Debounce {
|
||||
bool long_press_occurred_{false}; // TRUE when button is being held down and LONG_PRESS_DELAY has been reached (only when long_press_enabled)
|
||||
};
|
||||
|
||||
class EncoderDebounce {
|
||||
public:
|
||||
bool feed(const uint8_t phase_bits); // returns TRUE if state changed after debouncing
|
||||
|
||||
uint8_t state(); // returns debounced phase bits from encoder
|
||||
|
||||
uint8_t rotation_rate(); // returns last rotation rate
|
||||
|
||||
private:
|
||||
uint8_t history_{0}; // shift register of previous reads from encoder
|
||||
|
||||
uint8_t state_{0}; // actual encoder output state (after debounce logic)
|
||||
|
||||
uint8_t last_rotation_rate_{1};
|
||||
uint8_t rotation_rate_downcounter_{1}; // down-counter to estimate rotation speed
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,82 +27,51 @@
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
// Now supporting multiple levels of rotary encoder dial sensitivity
|
||||
//
|
||||
// Portapack H2 normally has a 30-step encoder, meaning one step (pulse) every
|
||||
// 12 degrees of rotation.
|
||||
//
|
||||
// For each encoder "pulse" there are 4 state transitions, and we can choose
|
||||
// between looking at all of them (high sensitivity), half of them (medium/default),
|
||||
// or one quarter of them (low sensitivity).
|
||||
static const int8_t transition_map[][16] = {
|
||||
// Normal (Medium) Sensitivity -- default
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Low Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
0, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
0, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// High Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Transition map for rotary encoder phase bits
|
||||
// 00 -> 01 -> 11 -> 10 cw
|
||||
// 00 -> 10 -> 11 -> 01 ccw
|
||||
// NB: Bits are swapped versus older FW versions
|
||||
static const int8_t transition_map[16] = {
|
||||
0, // 00->00: noop
|
||||
1, // 00->01: cw start
|
||||
-1, // 00->10: ccw start
|
||||
0, // 00->11: rate
|
||||
-1, // 01->00: ccw end
|
||||
0, // 01->01: noop
|
||||
0, // 01->10: rate
|
||||
1, // 01->11: cw end
|
||||
1, // 10->00: cw end
|
||||
0, // 10->01: rate
|
||||
0, // 10->10: noop
|
||||
-1, // 10->11: ccw end
|
||||
0, // 11->00: rate
|
||||
-1, // 11->01: ccw start
|
||||
1, // 11->10: cw start
|
||||
0, // 11->11: noop
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1) {
|
||||
state <<= 1;
|
||||
state |= phase_0;
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
// Rotary encoder dial sensitivity (transition ignored if bit is 0)
|
||||
static const uint16_t sensitivity_map[] = {
|
||||
0x0990, // DIAL_SENSITIVITY_NORMAL
|
||||
0x0110, // DIAL_SENSITIVITY_LOW
|
||||
0x6996, // DIAL_SENSITIVITY_HIGH
|
||||
};
|
||||
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
state = ((state << 2) | phase_bits) & 0x0F;
|
||||
|
||||
int_fast8_t direction = transition_map[state];
|
||||
|
||||
// Require 2 state changes in same direction to register movement -- for additional level of contact switch debouncing
|
||||
if (direction == prev_direction) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
return direction;
|
||||
}
|
||||
|
||||
// It's normal for transition map to return 0 between every +1/-1, so discarding those
|
||||
if (direction != 0)
|
||||
prev_direction = direction;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,12 +27,11 @@
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
int_fast8_t update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1);
|
||||
int_fast8_t update(const uint_fast8_t phase_bits);
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t prev_direction{0};
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -149,7 +149,33 @@ void MAX2837::init() {
|
||||
set_mode(Mode::Standby);
|
||||
}
|
||||
|
||||
enum class Mask {
|
||||
void MAX2837::set_tx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* TX IQ phase deg CAL adj (+4 ...-4) in 32 steps (5 bits), 00000 = +4deg (Q lags I by 94degs, default), 01111 = +0deg, 11111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// TX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,0,1 (5dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output();
|
||||
gpio_max2837_rxenable.output();
|
||||
gpio_max2837_txenable.output();
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.tx_lo_iq.TXLO_IQ_SPI_EN = 1; // reg 30 D5, TX LO I/Q Phase SPI Adjust. Active when Address 30 D5 (TXLO_IQ_SPI_EN) = 1.
|
||||
_map.r.tx_lo_iq.TXLO_IQ_SPI = v; // reg 30 D4:D0, TX LO I/Q Phase SPI Adjust.
|
||||
flush_one(Register::TX_LO_IQ);
|
||||
|
||||
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
|
||||
}
|
||||
|
||||
enum class Mask { // There are class Mask ,and class mode with same names, but they are not same.
|
||||
Enable = 0b001,
|
||||
RxEnable = 0b010,
|
||||
TxEnable = 0b100,
|
||||
@@ -157,9 +183,11 @@ enum class Mask {
|
||||
Standby = Enable,
|
||||
Receive = Enable | RxEnable,
|
||||
Transmit = Enable | TxEnable,
|
||||
Rx_calibration = Enable | RxEnable, // sets the same 3 x logic pins to the Receive operating mode.
|
||||
Tx_calibration = Enable | TxEnable, // sets the same 3 x logic pins to the Transmit operating mode.
|
||||
};
|
||||
|
||||
Mask mode_mask(const Mode mode) {
|
||||
Mask mode_mask(const Mode mode) { // based on enum Mode cases, we set up the correct 3 logic PINS .
|
||||
switch (mode) {
|
||||
case Mode::Standby:
|
||||
return Mask::Standby;
|
||||
@@ -167,12 +195,16 @@ Mask mode_mask(const Mode mode) {
|
||||
return Mask::Receive;
|
||||
case Mode::Transmit:
|
||||
return Mask::Transmit;
|
||||
case Mode::Rx_Calibration: // Let's add those two CAL logic pin settings- Rx and Tx calibration modes.
|
||||
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxEnable, (the difference is in Reg add 16 D1:DO)
|
||||
case Mode::Tx_Calibration: // Let's add this CAL Tx calibration mode = Transmit.
|
||||
return Mask::Tx_calibration; // same logic pins as Transmit = Enable | TxEnable,(the difference is in Reg add 16 D1:DO)
|
||||
default:
|
||||
return Mask::Shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
void MAX2837::set_mode(const Mode mode) {
|
||||
void MAX2837::set_mode(const Mode mode) { // We set up the 3 Logic Pins ENABLE, RXENABLE, TXENABLE accordingly to the max2837 mode case, that we want to set up .
|
||||
Mask mask = mode_mask(mode);
|
||||
gpio_max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
|
||||
gpio_max2837_rxenable.write(toUType(mask) & toUType(Mask::RxEnable));
|
||||
@@ -292,14 +324,41 @@ bool MAX2837::set_frequency(const rf::Frequency lo_frequency) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MAX2837::set_rx_lo_iq_calibration(const size_t v) {
|
||||
/*
|
||||
void MAX2837::set_rx_lo_iq_calibration(const size_t v) { // Original code , rewritten below
|
||||
_map.r.rx_top_rx_bias.RX_IQERR_SPI_EN = 1;
|
||||
_dirty[Register::RX_TOP_RX_BIAS] = 1;
|
||||
_map.r.rxrf_2.iqerr_trim = v;
|
||||
_dirty[Register::RXRF_2] = 1;
|
||||
flush();
|
||||
}
|
||||
*/
|
||||
|
||||
void MAX2837::set_rx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* RX IQ phase deg CAL adj (+4 ...-4) in 32 steps (5 bits), 00000 = +4deg (Q lags I by 94degs, default), 01111 = +0deg, 11111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// RX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,1,0 (3dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Rx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output();
|
||||
gpio_max2837_rxenable.output();
|
||||
gpio_max2837_txenable.output();
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.rx_top_rx_bias.RX_IQERR_SPI_EN = 1; // reg 8 D9, RX LO IQ Phase calibration SPI control. Active when Address 8 D<9> = 1.
|
||||
flush_one(Register::RX_TOP_RX_BIAS);
|
||||
|
||||
_map.r.rxrf_2.iqerr_trim = v; // reg 1 D9:D5, RX LO I/Q Phase SPI 5 bits Adjust
|
||||
flush_one(Register::RXRF_2);
|
||||
|
||||
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
|
||||
flush_one(Register::SPI_EN);
|
||||
}
|
||||
|
||||
void MAX2837::set_rx_bias_trim(const size_t v) {
|
||||
_map.r.rx_top_rx_bias.EN_Bias_Trim = 1;
|
||||
|
||||
@@ -828,7 +828,8 @@ class MAX2837 : public MAX283x {
|
||||
|
||||
bool set_frequency(const rf::Frequency lo_frequency) override;
|
||||
|
||||
void set_rx_lo_iq_calibration(const size_t v) override;
|
||||
void set_rx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_tx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_rx_bias_trim(const size_t v);
|
||||
void set_vco_bias(const size_t v);
|
||||
void set_rx_buff_vcm(const size_t v) override;
|
||||
|
||||
@@ -142,6 +142,31 @@ void MAX2839::init() {
|
||||
set_mode(Mode::Standby);
|
||||
}
|
||||
|
||||
void MAX2839::set_tx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* IQ phase deg CAL adj (+4 ...-4) This IC in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// TX calibration , 2 x Logic pins , ENABLE, RXENABLE = 1,0, (2dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
|
||||
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.pa_drv.TXLO_IQ_SPI_EN = 1; // reg 27 D6, TX LO I/Q Phase SPI Adjust. Active when Address 27 D6 (TXLO_IQ_SPI_EN) = 1.
|
||||
_map.r.pa_drv.TXLO_IQ_SPI = v; // reg 27 D5:D0 6 bits, TX LO I/Q Phase SPI Adjust.
|
||||
flush_one(Register::PA_DRV);
|
||||
|
||||
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
|
||||
}
|
||||
|
||||
enum class Mask {
|
||||
Enable = 0b01,
|
||||
RxTx = 0b10,
|
||||
@@ -149,6 +174,8 @@ enum class Mask {
|
||||
Standby = RxTx,
|
||||
Receive = Enable | RxTx,
|
||||
Transmit = Enable,
|
||||
Rx_calibration = Enable | RxTx, // sets the same 2 x logic pins to the Receive operating mode.
|
||||
Tx_calibration = Enable, // sets the same 2 x logic pins to the Transmit operating mode.
|
||||
};
|
||||
|
||||
Mask mode_mask(const Mode mode) {
|
||||
@@ -159,6 +186,10 @@ Mask mode_mask(const Mode mode) {
|
||||
return Mask::Receive;
|
||||
case Mode::Transmit:
|
||||
return Mask::Transmit;
|
||||
case Mode::Rx_Calibration: // Let's add this not used previously Rx calibration mode.
|
||||
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxTx, ,(the difference is in Regs )
|
||||
case Mode::Tx_Calibration: // Let's add this not used previously Tx calibration mode.
|
||||
return Mask::Tx_calibration; // same logic pins as Transmit = Enable , (the difference is in Reg add 16 D1:DO)
|
||||
default:
|
||||
return Mask::Shutdown;
|
||||
}
|
||||
@@ -336,13 +367,38 @@ bool MAX2839::set_frequency(const rf::Frequency lo_frequency) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MAX2839::set_rx_lo_iq_calibration(const size_t v) {
|
||||
/*
|
||||
void MAX2839::set_rx_LO_iq_phase_calibration(const size_t v) { // Original code , rewritten below
|
||||
_map.r.rxrf_2.RX_IQERR_SPI_EN = 1;
|
||||
_dirty[Register::RXRF_2] = 1;
|
||||
_map.r.rxrf_1.iqerr_trim = v;
|
||||
_dirty[Register::RXRF_1] = 1;
|
||||
flush();
|
||||
}*/
|
||||
|
||||
void MAX2839::set_rx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* RX IQ phase deg CAL adj (+4 ...-4) in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// RX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,1,0 (3dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Rx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
|
||||
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.rxrf_2.RX_IQERR_SPI_EN = 1; // reg 2 D<2> = 1, RX LO IQ calibration SPI control. Active when Address 2 D<2> = 1.
|
||||
_dirty[Register::RXRF_2] = 1;
|
||||
|
||||
_map.r.rxrf_1.iqerr_trim = v;
|
||||
_dirty[Register::RXRF_1] = 1;
|
||||
flush();
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
}
|
||||
|
||||
void MAX2839::set_rx_buff_vcm(const size_t v) {
|
||||
|
||||
@@ -82,11 +82,11 @@ struct RXENABLE_Type {
|
||||
static_assert(sizeof(RXENABLE_Type) == sizeof(reg_t), "RXENABLE_Type wrong size");
|
||||
|
||||
struct RXRF_1_Type {
|
||||
reg_t LNAband : 1;
|
||||
reg_t LNAband : 2; // Datasheet says D1:D0 , 2 bits, maybe D1 Rx_B, D0 Rx_A , (original code said wrongly ,reg_t LNAband : 1; that was of for max2837, not max2839 )
|
||||
reg_t RESERVED0 : 1;
|
||||
reg_t MIMOmode : 1;
|
||||
reg_t iqerr_trim : 5;
|
||||
reg_t RESERVED1 : 6;
|
||||
reg_t iqerr_trim : 6; // Datasheet says D9_D4 , that means 6 bits, (original code said wrongly ,reg_t iqerr_trim : 5; )
|
||||
reg_t RESERVED1 : 6; // we are using 16 bits , even top part mapping is not used
|
||||
};
|
||||
|
||||
static_assert(sizeof(RXRF_1_Type) == sizeof(reg_t), "RXRF_1_Type wrong size");
|
||||
@@ -689,7 +689,8 @@ class MAX2839 : public MAX283x {
|
||||
void set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) override;
|
||||
void set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) override;
|
||||
bool set_frequency(const rf::Frequency lo_frequency) override;
|
||||
void set_rx_lo_iq_calibration(const size_t v) override;
|
||||
void set_rx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_tx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_rx_buff_vcm(const size_t v) override;
|
||||
|
||||
int8_t temp_sense() override;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user