mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-22 15:39:04 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb22af84ff | |||
| 6dc25813e2 | |||
| 4ee00c0031 | |||
| 55525ed4ad | |||
| 84dcfe29a7 | |||
| 1ebe956557 | |||
| ae656f07bf | |||
| d5c894b322 | |||
| 9cea76a9f3 | |||
| ead9449609 | |||
| fff63e056a | |||
| 42da7443b4 | |||
| ee10081854 | |||
| b38d1012fa | |||
| 5075349d2d | |||
| 8497b62c13 | |||
| fdeaf380d1 | |||
| 67eb8e9104 | |||
| 989414d4df | |||
| 509bda2472 | |||
| f5e6cb0d96 | |||
| 8e2442f196 | |||
| ac89ca2a2a | |||
| 498369b77d | |||
| 2e64fae953 | |||
| 2d8e0f88a3 | |||
| 874eba8b36 | |||
| c553df7170 | |||
| 67a804c5ba | |||
| 9a14d04c91 | |||
| e17aef3724 | |||
| 4155216a7f | |||
| d9bc542b06 | |||
| 3dcfa4f8c1 | |||
| d36ae88284 | |||
| 69271632ae | |||
| e6a099913a | |||
| 280b8afc0a | |||
| b108d975c0 |
@@ -54,19 +54,22 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -38,19 +38,22 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.1
|
||||
v2.0.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.2
|
||||
v2.1.0
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "hackrf"]
|
||||
path = hackrf
|
||||
url = https://github.com/mossmann/hackrf.git
|
||||
url = https://github.com/portapack-mayhem/hackrf.git
|
||||
|
||||
+2
-1
@@ -65,10 +65,11 @@ message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
||||
|
||||
add_subdirectory(hackrf-portapack)
|
||||
add_subdirectory(hackrf/firmware)
|
||||
|
||||
set(HACKRF_FIRMWARE_DFU_FILENAME hackrf_usb.dfu)
|
||||
set(HACKRF_FIRMWARE_BIN_FILENAME hackrf_usb_ram.bin)
|
||||
set(HACKRF_FIRMWARE_FILENAME hackrf_usb.bin)
|
||||
set(HACKRF_CPLD_XSVF_FILENAME default.xsvf)
|
||||
|
||||
set(HACKRF_PATH ${CMAKE_CURRENT_LIST_DIR}/hackrf)
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
<!---
|
||||
> [!WARNING]
|
||||
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
|
||||
>
|
||||
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.
|
||||
|
||||
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://grabify.link/P9CO0J) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure)*
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
|
||||
# What is this?
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||
|
||||
[<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="701">](https://grabify.link/F9MPOO)
|
||||
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="701">](https://share.hackrf.app/6HKX9A)
|
||||
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://grabify.link/X4D5TF) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="172">](https://grabify.link/9UZGEW) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://grabify.link/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://grabify.link/C0J6ZR)
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF) [<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://share.hackrf.app/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://share.hackrf.app/C0J6ZR)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
@@ -27,12 +25,12 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://grabify.link/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous [PortaPack H4M Mayhem](https://grabify.link/0STXCA), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://grabify.link/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/TUOLYI) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/P9CO0J), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||
|
||||
|
||||
+2
-11
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM ubuntu:noble
|
||||
|
||||
# Set location to download ARM toolkit from.
|
||||
# This will need to be changed over time or replaced with a static link to the latest release.
|
||||
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
|
||||
|
||||
# Fetch dependencies from APT
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#Install current pip from PyPa
|
||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||
python3 get-pip.py
|
||||
|
||||
#Fetch additional dependencies from Python 3.x pip
|
||||
RUN pip install pyyaml \
|
||||
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
|
||||
+2
-11
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM ubuntu:noble
|
||||
|
||||
# Set location to download ARM toolkit from.
|
||||
# This will need to be changed over time or replaced with a static link to the latest release.
|
||||
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
|
||||
|
||||
# Fetch dependencies from APT
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#Install current pip from PyPa
|
||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||
python3 get-pip.py
|
||||
|
||||
#Fetch additional dependencies from Python 3.x pip
|
||||
RUN pip install pyyaml \
|
||||
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
|
||||
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
|
||||
set(LZ4 lz4)
|
||||
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_NAME portapack-mayhem-firmware)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||
|
||||
@@ -64,7 +64,7 @@ add_custom_command(
|
||||
|
||||
add_custom_target(
|
||||
firmware
|
||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} ${HACKRF_FIRMWARE_FILENAME}
|
||||
)
|
||||
|
||||
if(${GCC_VERSION_MISMATCH})
|
||||
|
||||
@@ -1,58 +1,60 @@
|
||||
#include "ui_about_simple.hpp"
|
||||
#include <string_view>
|
||||
|
||||
#define ROLL_SPEED_FRAME_PER_LINE 60
|
||||
// cuz frame rate of pp screen is probably 60, scroll per sec
|
||||
|
||||
namespace ui {
|
||||
|
||||
// TODO: Generate this automatically from github
|
||||
// Information: a line starting with a '#' will be yellow coloured
|
||||
const std::string authors_list[] = {
|
||||
constexpr std::string_view authors_list[] = {
|
||||
"# * List of contributors * ",
|
||||
" ",
|
||||
"#Mayhem:",
|
||||
"eried,euquiq,gregoryfenton",
|
||||
"johnelder,jwetzell,nnemanjan00",
|
||||
"N0vaPixel,klockee,gullradriel",
|
||||
"jamesshao8,ITAxReal,rascafr",
|
||||
"mcules,dqs105,strijar,fossum",
|
||||
"zhang00963,RedFox-Fr,aldude999",
|
||||
"East2West,ArjanOnwezen,jLynx",
|
||||
"vXxOinvizioNxX,teixeluis,n0xa",
|
||||
"Brumi-2021,texasyojimbo,zigad",
|
||||
"heurist1,intoxsick,ckuethe",
|
||||
"notpike,MichalLeonBorsuk",
|
||||
"kallanreed,bernd-herzog,zxkmm",
|
||||
"NotherNgineer,jimilinuxguy",
|
||||
"iNetro,HTotoo,BehleZebub,dc2dc",
|
||||
"arneluehrs,u-foka,Maescool",
|
||||
"nnesetto,formtapez,F33RNI",
|
||||
"rusty-labs,andrej-mk,taskinen",
|
||||
"MatiasFernandez,TQMatvey,KimIV",
|
||||
"RobertoD91,RndmNmbr,OpCode1300",
|
||||
"alex10791,GitClo,GermanAizek",
|
||||
"JimiHFord,jstockdale,aSmig",
|
||||
"KillerTurtleSoftware,Lpd738",
|
||||
"MattLodge,z3r0l1nk,joyel24",
|
||||
"#Mayhem-Firmware:",
|
||||
"jboone,eried,furrtek,",
|
||||
"NotherNgineer,gullradriel,",
|
||||
"jLynx,kallanreed,Brumi-2021,",
|
||||
"htotoo,bernd-herzog,zxkmm,",
|
||||
"ArjanOnwezen,euquiq,u-foka,",
|
||||
"iNetro,heurist1,dqs105,",
|
||||
"teixeluis,jwetzell,",
|
||||
"jimilinuxguy,gregoryfenton,",
|
||||
"notpike,strijar,BehleZebub,",
|
||||
"arneluehrs,rascafr,joyel24,",
|
||||
"ImDroided,zigad,johnelder,",
|
||||
"klockee,nnesetto,LupusE,",
|
||||
"argilo,dc2dc,formtapez,",
|
||||
"RocketGod-git,mrmookie,",
|
||||
"ITAxReal,F33RNI,F4GEV,",
|
||||
"rusty-labs,mjwaxios,andrej-mk,",
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox,",
|
||||
"ckuethe",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"furrtek,mrmookie,NotPike",
|
||||
"mjwaxios,ImDroided,Giorgiofox",
|
||||
"F4GEV,z4ziggy,xmycroftx",
|
||||
"troussos,silascutler",
|
||||
"nickbouwhuis,msoose,leres",
|
||||
"joakar,dhoetger,clem-42",
|
||||
"brianlechthaler,ZeroChaos-...",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
"mrmookie,Giorgiofox,ImDroided,",
|
||||
"mjwaxios,F4GEV,OpCode1300,",
|
||||
"ZeroChaos-,RndmNmbr,",
|
||||
"silascutler,troussos,z4ziggy,",
|
||||
"clem-42,dhoetger,NickBouwhuis,",
|
||||
"xmycroftx,Maescool,KimIV,",
|
||||
"joakar,leres,brianlechthaler,",
|
||||
"N0vaPixel",
|
||||
" ",
|
||||
"#PortaPack:",
|
||||
"jboone,argilo",
|
||||
"jboone,mossmann,martinling,",
|
||||
"argilo,eried,ZeroChaos-,",
|
||||
"RndmNmbr",
|
||||
" ",
|
||||
"#HackRF:",
|
||||
"mossmann,dominicgs,bvernoux",
|
||||
"bgamari,schneider42,miek",
|
||||
"willcode,hessu,Sec42",
|
||||
"yhetti,ckuethe,smunaut",
|
||||
"wishi,mrbubble62,scateu..."};
|
||||
"mossmann,jboone,dominicgs,",
|
||||
"martinling,bvernoux,miek,",
|
||||
"bgamari,schneider42,straithe,",
|
||||
"grvvy,willcode,hessu,yhetti,",
|
||||
"Sec42,ckuethe",
|
||||
" "};
|
||||
|
||||
AboutView::AboutView(NavigationView& nav) {
|
||||
add_children({&menu_view,
|
||||
@@ -70,18 +72,18 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (const std::string& authors_line : authors_list) {
|
||||
for (auto& authors_line : authors_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
menu_view.add_item(
|
||||
{authors_line.substr(1, authors_line.size() - 1),
|
||||
{(std::string)authors_line.substr(1, authors_line.size() - 1),
|
||||
ui::Theme::getInstance()->fg_yellow->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
} else {
|
||||
menu_view.add_item(
|
||||
{authors_line,
|
||||
{(std::string)authors_line,
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
|
||||
@@ -74,91 +74,6 @@ void DebugMemoryView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* TemperatureWidget *****************************************************/
|
||||
|
||||
void TemperatureWidget::paint(Painter& painter) {
|
||||
const auto logger = portapack::temperature_logger;
|
||||
|
||||
const auto rect = screen_rect();
|
||||
const Color color_background{0, 0, 64};
|
||||
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
|
||||
const Color color_reticle{128, 128, 128};
|
||||
|
||||
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
||||
const Rect graph_rect{
|
||||
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
|
||||
graph_width, rect.height()};
|
||||
const Rect frame_rect{
|
||||
graph_rect.left() - 1, graph_rect.top() - 1,
|
||||
graph_rect.width() + 2, graph_rect.height() + 2};
|
||||
painter.draw_rectangle(frame_rect, color_reticle);
|
||||
painter.fill_rectangle(graph_rect, color_background);
|
||||
|
||||
const auto history = logger.history();
|
||||
for (size_t i = 0; i < history.size(); i++) {
|
||||
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
||||
const auto sample = history[i];
|
||||
const auto temp = temperature(sample);
|
||||
const auto y = screen_y(temp, graph_rect);
|
||||
const Dim bar_height = graph_rect.bottom() - y;
|
||||
painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
|
||||
}
|
||||
|
||||
if (!history.empty()) {
|
||||
const auto sample = history.back();
|
||||
const auto temp = temperature(sample);
|
||||
const auto last_y = screen_y(temp, graph_rect);
|
||||
const Coord x = graph_rect.right() + 8;
|
||||
const Coord y = last_y - 8;
|
||||
|
||||
painter.draw_string({x, y}, style(), temperature_str(temp));
|
||||
}
|
||||
|
||||
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
|
||||
for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
|
||||
const int32_t tick_length = 6;
|
||||
const auto tick_x = graph_rect.left() - tick_length;
|
||||
const auto tick_y = screen_y(temp, graph_rect);
|
||||
painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
|
||||
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
|
||||
const auto text_y = tick_y - 8;
|
||||
painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
|
||||
// Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
|
||||
return sensor_value;
|
||||
}
|
||||
|
||||
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
|
||||
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
|
||||
}
|
||||
|
||||
Coord TemperatureWidget::screen_y(
|
||||
const temperature_t temperature,
|
||||
const Rect& rect) const {
|
||||
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
|
||||
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
|
||||
return y_limit;
|
||||
}
|
||||
|
||||
/* TemperatureView *******************************************************/
|
||||
|
||||
TemperatureView::TemperatureView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&temperature_widget,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
}
|
||||
|
||||
void TemperatureView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* RegistersWidget *******************************************************/
|
||||
|
||||
RegistersWidget::RegistersWidget(
|
||||
@@ -507,12 +422,9 @@ void DebugMenuView::on_populate() {
|
||||
{"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
|
||||
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
|
||||
//{"Memory Usage", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryView>(); }},
|
||||
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
|
||||
//{ "Radio State", ui::Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ nav_.push<NotImplementedView>(); } },
|
||||
{"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
|
||||
{"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
|
||||
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
|
||||
{"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<ExternalModuleView>(); }},
|
||||
|
||||
@@ -86,53 +86,6 @@ class DebugMemoryView : public View {
|
||||
"Done"};
|
||||
};
|
||||
|
||||
class TemperatureWidget : public Widget {
|
||||
public:
|
||||
explicit TemperatureWidget(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect} {
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
using sample_t = uint32_t;
|
||||
using temperature_t = int32_t;
|
||||
|
||||
temperature_t temperature(const sample_t sensor_value) const;
|
||||
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
|
||||
|
||||
std::string temperature_str(const temperature_t temperature) const;
|
||||
|
||||
static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
|
||||
static constexpr temperature_t display_temp_scale = 3;
|
||||
static constexpr int bar_width = 1;
|
||||
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
|
||||
};
|
||||
|
||||
class TemperatureView : public View {
|
||||
public:
|
||||
explicit TemperatureView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Temperature"; };
|
||||
|
||||
private:
|
||||
Text text_title{
|
||||
{76, 16, 240, 16},
|
||||
"Temperature",
|
||||
};
|
||||
|
||||
TemperatureWidget temperature_widget{
|
||||
{0, 40, 240, 180},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
{72, 264, 96, 24},
|
||||
"Done"};
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
CT_PMEM,
|
||||
CT_RFFC5072,
|
||||
|
||||
@@ -751,10 +751,17 @@ FileManagerView::FileManagerView(
|
||||
text_date.set("Too many files!");
|
||||
} else {
|
||||
text_date.set_style(Theme::getInstance()->fg_medium);
|
||||
if (selected_is_valid())
|
||||
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
if (selected_is_valid()) {
|
||||
if (get_selected_entry().path == str_back) {
|
||||
text_date.set("Go page " + std::to_string(pagination + 1 - 1)); // for better explain, pagination start with 0 AKA real page - 1
|
||||
} else if (get_selected_entry().path == str_next) {
|
||||
text_date.set("Go page " + std::to_string(pagination + 1 + 1)); // when show this, it should display current AKA (pagination + 1) + 1 AKA next page
|
||||
} else {
|
||||
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
}
|
||||
} else {
|
||||
text_date.set("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
|
||||
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
||||
color,
|
||||
&bitmap_icon_temperature,
|
||||
&bitmap_icon_peripherals_details,
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
|
||||
@@ -40,27 +40,39 @@ namespace fs = std::filesystem;
|
||||
|
||||
// TODO: Clean up after moving to better lookup tables.
|
||||
using options_t = OptionsField::options_t;
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_modulations;
|
||||
extern options_db_t freqman_bandwidths[4];
|
||||
extern options_db_t freqman_steps;
|
||||
extern options_db_t freqman_steps_short;
|
||||
|
||||
options_t dboptions_to_options(const options_db_t& dboptions) {
|
||||
options_t options;
|
||||
for (const auto& dboption : dboptions) {
|
||||
options.emplace_back(dboption.first, dboption.second);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_modulations);
|
||||
option.set_options(dboptions_to_options(freqman_modulations));
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
option.set_options(dboptions_to_options(freqman_bandwidths[modulation]));
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_steps);
|
||||
option.set_options(dboptions_to_options(freqman_steps));
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_steps_short);
|
||||
option.set_options(dboptions_to_options(freqman_steps_short));
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
@@ -438,7 +450,7 @@ void FrequencyEditView::populate_bandwidth_options() {
|
||||
auto& bandwidths = freqman_bandwidths[entry_.modulation];
|
||||
for (auto i = 0u; i < bandwidths.size(); ++i) {
|
||||
auto& item = bandwidths[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
options.push_back({(std::string)item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +463,7 @@ void FrequencyEditView::populate_step_options() {
|
||||
|
||||
for (auto i = 0u; i < freqman_steps.size(); ++i) {
|
||||
auto& item = freqman_steps[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
options.push_back({(std::string)item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
|
||||
field_step.set_options(std::move(options));
|
||||
|
||||
@@ -187,6 +187,7 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (last_max_db != statistics.max_db) {
|
||||
last_max_db = statistics.max_db;
|
||||
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
|
||||
rssi.set_db(statistics.max_db);
|
||||
}
|
||||
// refresh rssi
|
||||
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
|
||||
|
||||
@@ -2531,44 +2531,6 @@ static constexpr Bitmap bitmap_icon_batt_icon{
|
||||
{16, 16},
|
||||
bitmap_icon_batt_icon_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_temperature_data[] = {
|
||||
0x00,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x05,
|
||||
0xC0,
|
||||
0x0D,
|
||||
0x40,
|
||||
0x0D,
|
||||
0xD0,
|
||||
0x1F,
|
||||
0x70,
|
||||
0x15,
|
||||
0xB0,
|
||||
0x1A,
|
||||
0x58,
|
||||
0x35,
|
||||
0xB8,
|
||||
0x3A,
|
||||
0x58,
|
||||
0x34,
|
||||
0x28,
|
||||
0x28,
|
||||
0x18,
|
||||
0x30,
|
||||
0x30,
|
||||
0x18,
|
||||
0x60,
|
||||
0x0C,
|
||||
0xC0,
|
||||
0x07,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_temperature{
|
||||
{16, 16},
|
||||
bitmap_icon_temperature_data};
|
||||
|
||||
static constexpr uint8_t bitmap_tab_edge_data[] = {
|
||||
0x00,
|
||||
0x01,
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_acars_rx.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_adsbtx.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_adsbtx */ {'P', 'A', 'D', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_afsk_rx.application_information"), use
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_analogtv.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_am_tv */ {'P', 'A', 'M', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_audio_test.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::audio_test {
|
||||
|
||||
AudioTestView::AudioTestView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
@@ -103,4 +103,4 @@ void AudioTestView::update_audio_beep() {
|
||||
baseband::request_beep_stop();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::audio_test */
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::audio_test {
|
||||
|
||||
class AudioTestView : public View {
|
||||
public:
|
||||
@@ -96,6 +96,6 @@ class AudioTestView : public View {
|
||||
Theme::getInstance()->bg_dark->background};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::audio_test */
|
||||
|
||||
#endif /*__UI_AUDIO_TEST_H__*/
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_blespam.application_information"), use
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_calculator.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_coasterp.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+14
-13
@@ -17,17 +17,18 @@ void initialize_app(NavigationView& nav) {
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::cvs_spam::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"CVS Spam",
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
|
||||
ui::Color::red().v,
|
||||
app_location_t::TX,
|
||||
{'P', 'R', 'E', 'P'},
|
||||
0x00000000};
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::cvs_spam::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "CVS Spam",
|
||||
/*.bitmap_data = */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
|
||||
/*.icon_color = */ ui::Color::red().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'R', 'E', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+21
-1
@@ -130,6 +130,22 @@ set(EXTCPPSRC
|
||||
#remote
|
||||
external/remote/main.cpp
|
||||
external/remote/ui_remote.cpp
|
||||
|
||||
#mcu_temperature
|
||||
external/mcu_temperature/main.cpp
|
||||
external/mcu_temperature/mcu_temperature.cpp
|
||||
|
||||
#fmradio
|
||||
external/fmradio/main.cpp
|
||||
external/fmradio/ui_fmradio.cpp
|
||||
|
||||
#tuner
|
||||
external/tuner/main.cpp
|
||||
external/tuner/ui_tuner.cpp
|
||||
|
||||
#metronome
|
||||
external/metronome/main.cpp
|
||||
external/metronome/ui_metronome.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -163,5 +179,9 @@ set(EXTAPPLIST
|
||||
ook_editor
|
||||
shoppingcart_lock
|
||||
flippertx
|
||||
remote
|
||||
remote
|
||||
mcu_temperature
|
||||
fmradio
|
||||
tuner
|
||||
metronome
|
||||
)
|
||||
|
||||
+28
@@ -54,6 +54,10 @@ MEMORY
|
||||
ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k
|
||||
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
|
||||
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
|
||||
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
|
||||
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
|
||||
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
|
||||
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -244,4 +248,28 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_remote.application_information));
|
||||
*(*ui*external_app*remote*);
|
||||
} > ram_external_app_remote
|
||||
|
||||
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
||||
*(*ui*external_app*mcu_temperature*);
|
||||
} > ram_external_app_mcu_temperature
|
||||
|
||||
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_fmradio.application_information));
|
||||
*(*ui*external_app*fmradio*);
|
||||
} > ram_external_app_fmradio
|
||||
|
||||
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tuner.application_information));
|
||||
*(*ui*external_app*tuner*);
|
||||
} > ram_external_app_tuner
|
||||
|
||||
.external_app_metronome : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_metronome.application_information));
|
||||
*(*ui*external_app*metronome*);
|
||||
} > ram_external_app_metronome
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_extsensors.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_flippertx.application_information"), u
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_ookstreaming */ {'P', 'O', 'S', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_fmradio.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::fmradio {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<FmRadioView>();
|
||||
}
|
||||
} // namespace ui::external_app::fmradio
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_fmradio.application_information"), used)) application_information_t _application_information_fmradio = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::fmradio::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "FM Radio",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x20,
|
||||
0x12,
|
||||
0x48,
|
||||
0x8A,
|
||||
0x51,
|
||||
0xCA,
|
||||
0x53,
|
||||
0xCA,
|
||||
0x53,
|
||||
0x8A,
|
||||
0x51,
|
||||
0x12,
|
||||
0x48,
|
||||
0x84,
|
||||
0x21,
|
||||
0xC0,
|
||||
0x03,
|
||||
0x40,
|
||||
0x02,
|
||||
0x60,
|
||||
0x06,
|
||||
0x20,
|
||||
0x04,
|
||||
0x30,
|
||||
0x0C,
|
||||
0xF0,
|
||||
0x0F,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_wfm_audio */ {'P', 'W', 'F', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_fmradio.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::fmradio {
|
||||
|
||||
void FmRadioView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
FmRadioView::FmRadioView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
|
||||
add_children({&rssi,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_volume,
|
||||
&field_frequency,
|
||||
&btn_fav_save,
|
||||
&txt_save_help,
|
||||
&btn_fav_0,
|
||||
&btn_fav_1,
|
||||
&btn_fav_2,
|
||||
&btn_fav_3,
|
||||
&btn_fav_4,
|
||||
&btn_fav_5,
|
||||
&btn_fav_6,
|
||||
&btn_fav_7,
|
||||
&btn_fav_8,
|
||||
&btn_fav_9,
|
||||
&audio,
|
||||
&waveform});
|
||||
|
||||
txt_save_help.visible(false);
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (freq_fav_list[i] == 0) {
|
||||
freq_fav_list[i] = 87000000;
|
||||
}
|
||||
}
|
||||
|
||||
if (field_frequency.value() == 0) {
|
||||
field_frequency.set_value(87000000);
|
||||
}
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
|
||||
field_frequency.set_step(25000);
|
||||
receiver_model.enable();
|
||||
audio::output::start();
|
||||
|
||||
btn_fav_0.on_select = [this](Button&) {
|
||||
on_btn_clicked(0);
|
||||
};
|
||||
btn_fav_1.on_select = [this](Button&) {
|
||||
on_btn_clicked(1);
|
||||
};
|
||||
btn_fav_2.on_select = [this](Button&) {
|
||||
on_btn_clicked(2);
|
||||
};
|
||||
btn_fav_3.on_select = [this](Button&) {
|
||||
on_btn_clicked(3);
|
||||
};
|
||||
btn_fav_4.on_select = [this](Button&) {
|
||||
on_btn_clicked(4);
|
||||
};
|
||||
btn_fav_5.on_select = [this](Button&) {
|
||||
on_btn_clicked(5);
|
||||
};
|
||||
btn_fav_6.on_select = [this](Button&) {
|
||||
on_btn_clicked(6);
|
||||
};
|
||||
btn_fav_7.on_select = [this](Button&) {
|
||||
on_btn_clicked(7);
|
||||
};
|
||||
btn_fav_8.on_select = [this](Button&) {
|
||||
on_btn_clicked(8);
|
||||
};
|
||||
btn_fav_9.on_select = [this](Button&) {
|
||||
on_btn_clicked(9);
|
||||
};
|
||||
|
||||
btn_fav_save.on_select = [this](Button&) {
|
||||
save_fav = !save_fav;
|
||||
txt_save_help.set_text(save_fav ? "Select slot" : "");
|
||||
txt_save_help.visible(save_fav);
|
||||
txt_save_help.set_dirty();
|
||||
};
|
||||
|
||||
update_fav_btn_texts();
|
||||
}
|
||||
|
||||
void FmRadioView::on_btn_clicked(uint8_t i) {
|
||||
if (save_fav) {
|
||||
save_fav = false;
|
||||
freq_fav_list[i] = field_frequency.value();
|
||||
update_fav_btn_texts();
|
||||
txt_save_help.visible(save_fav);
|
||||
txt_save_help.set_text("");
|
||||
txt_save_help.set_dirty();
|
||||
return;
|
||||
}
|
||||
field_frequency.set_value(freq_fav_list[i]);
|
||||
}
|
||||
|
||||
std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
|
||||
std::string nice = to_string_dec_uint(freq / 1000000);
|
||||
nice += ".";
|
||||
nice += to_string_dec_uint((freq / 10000) % 100);
|
||||
return nice;
|
||||
}
|
||||
|
||||
void FmRadioView::update_fav_btn_texts() {
|
||||
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0]));
|
||||
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1]));
|
||||
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2]));
|
||||
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3]));
|
||||
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4]));
|
||||
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5]));
|
||||
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6]));
|
||||
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7]));
|
||||
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8]));
|
||||
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9]));
|
||||
}
|
||||
|
||||
FmRadioView::~FmRadioView() {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
audio::output::stop();
|
||||
}
|
||||
|
||||
void FmRadioView::on_audio_spectrum() {
|
||||
for (size_t i = 0; i < audio_spectrum_data->db.size(); i++)
|
||||
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
|
||||
waveform.set_dirty();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::fmradio
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
FUTURE TODO: implement search function.
|
||||
*/
|
||||
|
||||
#ifndef __UI_fmradio_H__
|
||||
#define __UI_fmradio_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::fmradio {
|
||||
|
||||
#define FMR_BTNGRID_TOP 60
|
||||
|
||||
class FmRadioView : public View {
|
||||
public:
|
||||
FmRadioView(NavigationView& nav);
|
||||
FmRadioView& operator=(const FmRadioView&) = delete;
|
||||
FmRadioView(const FmRadioView&) = delete;
|
||||
~FmRadioView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "FM radio"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
int16_t audio_spectrum[128]{0};
|
||||
bool audio_spectrum_update = false;
|
||||
AudioSpectrum* audio_spectrum_data{nullptr};
|
||||
rf::Frequency freq_fav_list[12] = {0};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_fmradio",
|
||||
app_settings::Mode::RX,
|
||||
{{"favlist0"sv, &freq_fav_list[0]},
|
||||
{"favlist1"sv, &freq_fav_list[1]},
|
||||
{"favlist2"sv, &freq_fav_list[2]},
|
||||
{"favlist3"sv, &freq_fav_list[3]},
|
||||
{"favlist4"sv, &freq_fav_list[4]},
|
||||
{"favlist5"sv, &freq_fav_list[5]},
|
||||
{"favlist6"sv, &freq_fav_list[6]},
|
||||
{"favlist7"sv, &freq_fav_list[7]},
|
||||
{"favlist8"sv, &freq_fav_list[8]},
|
||||
{"favlist9"sv, &freq_fav_list[9]},
|
||||
{"favlist10"sv, &freq_fav_list[10]},
|
||||
{"favlist11"sv, &freq_fav_list[11]}}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
TextField txt_save_help{
|
||||
{2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
|
||||
" "};
|
||||
|
||||
Audio audio{
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 20, 30 * 8, 2 * 16},
|
||||
audio_spectrum,
|
||||
128,
|
||||
0,
|
||||
false,
|
||||
Theme::getInstance()->bg_darkest->foreground};
|
||||
|
||||
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_2{{2, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_3{{2 + 15 * 8, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_4{{2, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_5{{2 + 15 * 8, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_6{{2, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_7{{2 + 15 * 8, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_8{{2, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_9{{2 + 15 * 8, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
|
||||
|
||||
Button btn_fav_save{{2, FMR_BTNGRID_TOP + 6 * 34, 7 * 8, 1 * 28}, "Save"};
|
||||
bool save_fav = false;
|
||||
void on_btn_clicked(uint8_t i);
|
||||
void update_fav_btn_texts();
|
||||
std::string to_nice_freq(rf::Frequency freq);
|
||||
void on_audio_spectrum();
|
||||
|
||||
MessageHandlerRegistration message_handler_audio_spectrum{
|
||||
Message::ID::AudioSpectrum,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
|
||||
audio_spectrum_data = message.data;
|
||||
audio_spectrum_update = true;
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if (audio_spectrum_update) {
|
||||
audio_spectrum_update = false;
|
||||
on_audio_spectrum();
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::fmradio
|
||||
|
||||
#endif /*__UI_fmradio_H__*/
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_font_viewer.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_foxhunt_rx.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_am_audio */ {'P', 'A', 'M', 'A'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_gpssim.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_gpssim */ {'P', 'G', 'P', 'S'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_jammer.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_keyfob.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lcr.application_information"), used))
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lge.application_information"), used))
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "mcu_temperature.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::mcu_temperature {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<McuTemperatureView>();
|
||||
}
|
||||
} // namespace ui::external_app::mcu_temperature
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_mcu_temperature.application_information"), used)) application_information_t _application_information_mcu_temperature = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::mcu_temperature::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Temperature",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x70,
|
||||
0x3E,
|
||||
0x88,
|
||||
0x00,
|
||||
0x88,
|
||||
0x00,
|
||||
0x88,
|
||||
0x3E,
|
||||
0x88,
|
||||
0x00,
|
||||
0x88,
|
||||
0x00,
|
||||
0x88,
|
||||
0x3E,
|
||||
0x88,
|
||||
0x00,
|
||||
0x04,
|
||||
0x01,
|
||||
0x74,
|
||||
0x01,
|
||||
0x04,
|
||||
0x01,
|
||||
0x88,
|
||||
0x00,
|
||||
0x70,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "mcu_temperature.hpp"
|
||||
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace ui::external_app::mcu_temperature {
|
||||
|
||||
void McuTemperatureWidget::paint(Painter& painter) {
|
||||
const auto logger = portapack::temperature_logger;
|
||||
|
||||
const auto rect = screen_rect();
|
||||
const Color color_background{0, 0, 64};
|
||||
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
|
||||
const Color color_reticle{128, 128, 128};
|
||||
|
||||
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
||||
const Rect graph_rect{
|
||||
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
|
||||
graph_width, rect.height()};
|
||||
const Rect frame_rect{
|
||||
graph_rect.left() - 1, graph_rect.top() - 1,
|
||||
graph_rect.width() + 2, graph_rect.height() + 2};
|
||||
painter.draw_rectangle(frame_rect, color_reticle);
|
||||
painter.fill_rectangle(graph_rect, color_background);
|
||||
|
||||
const auto history = logger.history();
|
||||
for (size_t i = 0; i < history.size(); i++) {
|
||||
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
||||
const auto sample = history[i];
|
||||
const auto temp = temperature(sample);
|
||||
const auto y = screen_y(temp, graph_rect);
|
||||
const Dim bar_height = graph_rect.bottom() - y;
|
||||
painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
|
||||
}
|
||||
|
||||
if (!history.empty()) {
|
||||
const auto sample = history.back();
|
||||
const auto temp = temperature(sample);
|
||||
const auto last_y = screen_y(temp, graph_rect);
|
||||
const Coord x = graph_rect.right() + 8;
|
||||
const Coord y = last_y - 8;
|
||||
|
||||
painter.draw_string({x, y}, style(), temperature_str(temp));
|
||||
}
|
||||
|
||||
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
|
||||
for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
|
||||
const int32_t tick_length = 6;
|
||||
const auto tick_x = graph_rect.left() - tick_length;
|
||||
const auto tick_y = screen_y(temp, graph_rect);
|
||||
painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
|
||||
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
|
||||
const auto text_y = tick_y - 8;
|
||||
painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
|
||||
}
|
||||
}
|
||||
|
||||
McuTemperatureWidget::temperature_t McuTemperatureWidget::temperature(const sample_t sensor_value) const {
|
||||
// Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
|
||||
return sensor_value;
|
||||
}
|
||||
|
||||
std::string McuTemperatureWidget::temperature_str(const temperature_t temperature) const {
|
||||
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
|
||||
}
|
||||
|
||||
Coord McuTemperatureWidget::screen_y(
|
||||
const temperature_t temperature,
|
||||
const Rect& rect) const {
|
||||
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
|
||||
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
|
||||
return y_limit;
|
||||
}
|
||||
|
||||
McuTemperatureView::McuTemperatureView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&temperature_widget,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
}
|
||||
|
||||
void McuTemperatureView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::mcu_temperature
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __MCU_TEMPERATURE_H__
|
||||
#define __MCU_TEMPERATURE_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "rffc507x.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "memory_map.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace ui::external_app::mcu_temperature {
|
||||
|
||||
class McuTemperatureWidget : public Widget {
|
||||
public:
|
||||
explicit McuTemperatureWidget(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect} {
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
using sample_t = uint32_t;
|
||||
using temperature_t = int32_t;
|
||||
|
||||
temperature_t temperature(const sample_t sensor_value) const;
|
||||
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
|
||||
|
||||
std::string temperature_str(const temperature_t temperature) const;
|
||||
|
||||
static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
|
||||
static constexpr temperature_t display_temp_scale = 3;
|
||||
static constexpr int bar_width = 1;
|
||||
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
|
||||
};
|
||||
|
||||
class McuTemperatureView : public View {
|
||||
public:
|
||||
explicit McuTemperatureView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Temperature"; };
|
||||
|
||||
private:
|
||||
Text text_title{
|
||||
{76, 16, 240, 16},
|
||||
"Temperature",
|
||||
};
|
||||
|
||||
McuTemperatureWidget temperature_widget{
|
||||
{0, 40, 240, 180},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
{72, 264, 96, 24},
|
||||
"Done"};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::mcu_temperature
|
||||
|
||||
#endif /*__MCU_TEMPERATURE_H__*/
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_metronome.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::metronome {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<MetronomeView>();
|
||||
}
|
||||
} // namespace ui::external_app::metronome
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_metronome.application_information"), used)) application_information_t _application_information_metronome = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::metronome::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Metronome",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0xC0,
|
||||
0x43,
|
||||
0x20,
|
||||
0x66,
|
||||
0xA0,
|
||||
0x34,
|
||||
0x20,
|
||||
0x18,
|
||||
0xB0,
|
||||
0x0C,
|
||||
0x10,
|
||||
0x04,
|
||||
0x10,
|
||||
0x06,
|
||||
0x10,
|
||||
0x0B,
|
||||
0x98,
|
||||
0x19,
|
||||
0x08,
|
||||
0x10,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* copyleft 2024 sommermorgentraum
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_metronome.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::metronome {
|
||||
|
||||
MetronomeView::MetronomeView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_volume,
|
||||
&button_play_stop,
|
||||
&field_rythm_unaccent_time,
|
||||
&field_rythm_accent_time,
|
||||
&field_accent_beep_tune,
|
||||
&field_unaccent_beep_tune,
|
||||
&field_beep_flash_duration,
|
||||
&field_bpm,
|
||||
&progressbar,
|
||||
});
|
||||
|
||||
field_bpm.set_value(120);
|
||||
field_rythm_accent_time.set_value(4);
|
||||
field_rythm_unaccent_time.set_value(4);
|
||||
field_accent_beep_tune.set_value(880);
|
||||
field_unaccent_beep_tune.set_value(440);
|
||||
field_beep_flash_duration.set_value(100);
|
||||
button_play_stop.on_select = [this]() {
|
||||
if (playing_) {
|
||||
stop_play();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
};
|
||||
|
||||
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
|
||||
field_volume.set_value(99);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
MetronomeView::~MetronomeView() {
|
||||
should_exit = true;
|
||||
if (thread) {
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
}
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
audio::output::stop();
|
||||
}
|
||||
|
||||
void MetronomeView::focus() {
|
||||
field_bpm.focus();
|
||||
}
|
||||
|
||||
void MetronomeView::stop_play() {
|
||||
if (playing_) {
|
||||
playing_ = false;
|
||||
button_play_stop.set_bitmap(&bitmap_icon_replay);
|
||||
baseband::request_beep_stop();
|
||||
progressbar.set_value(0);
|
||||
progressbar.set_style(Theme::getInstance()->fg_light);
|
||||
}
|
||||
}
|
||||
|
||||
void MetronomeView::play() {
|
||||
if (!playing_) {
|
||||
playing_ = true;
|
||||
current_beat_ = 0;
|
||||
button_play_stop.set_bitmap(&bitmap_icon_sleep);
|
||||
|
||||
if (!thread) {
|
||||
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, MetronomeView::static_fn, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetronomeView::beep_accent_beat() {
|
||||
baseband::request_audio_beep(field_accent_beep_tune.value(), 48000, field_beep_flash_duration.value());
|
||||
}
|
||||
|
||||
void MetronomeView::beep_unaccent_beat() {
|
||||
baseband::request_audio_beep(field_unaccent_beep_tune.value(), 48000, field_beep_flash_duration.value());
|
||||
}
|
||||
|
||||
// TODO: draw the beat
|
||||
// void MetronomeView::paint(Painter& painter) {
|
||||
// View::paint(painter);
|
||||
|
||||
// painter.fill_rectangle(
|
||||
// {visual_x, visual_y, visual_width, visual_height},
|
||||
// Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
// if (playing_) {
|
||||
// const bool is_accent_beat = (current_beat_ % field_rythm_accent_time.value()) == 0;
|
||||
|
||||
// const Color beat_color = is_accent_beat ?
|
||||
// Theme::getInstance()->fg_red->foreground :
|
||||
// Theme::getInstance()->fg_green->foreground;
|
||||
|
||||
// painter.fill_rectangle(
|
||||
// {visual_x + visual_width/4,
|
||||
// visual_y + visual_height/4,
|
||||
// visual_width/2,
|
||||
// visual_height/2},
|
||||
// beat_color);
|
||||
// }
|
||||
// }
|
||||
|
||||
msg_t MetronomeView::static_fn(void* arg) {
|
||||
auto obj = static_cast<MetronomeView*>(arg);
|
||||
obj->run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MetronomeView::run() {
|
||||
while (!should_exit) {
|
||||
if (!playing_) {
|
||||
chThdSleepMilliseconds(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t base_interval = (60 * 1000) / field_bpm.value(); // quarter note as 1 beat
|
||||
|
||||
uint32_t beats_per_measure = field_rythm_unaccent_time.value(); // how many beates per bar
|
||||
progressbar.set_max(beats_per_measure);
|
||||
uint32_t beat_unit = field_rythm_accent_time.value(); // which note type (quarter, eighth, etc.) as 1 beat
|
||||
|
||||
uint32_t actual_interval = (base_interval * 4) / beat_unit; // e.g. when beat_unit==8 it's 1/2 of base_interval AKA eighths notes
|
||||
|
||||
uint32_t beat_in_measure = current_beat_ % beats_per_measure; // current beat in this bar (need to decide accent or unaccent)
|
||||
progressbar.set_value(beat_in_measure + 1);
|
||||
|
||||
// accent beat is the first beat of this bar
|
||||
if (beat_in_measure == 0) {
|
||||
beep_accent_beat();
|
||||
progressbar.set_style(Theme::getInstance()->fg_red);
|
||||
} else {
|
||||
beep_unaccent_beat();
|
||||
progressbar.set_style(Theme::getInstance()->fg_green);
|
||||
}
|
||||
|
||||
current_beat_++;
|
||||
chThdSleepMilliseconds(actual_interval);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::metronome
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* copyleft 2024 sommermorgentraum
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_METRONOME_H__
|
||||
#define __UI_METRONOME_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ch.h"
|
||||
|
||||
namespace ui::external_app::metronome {
|
||||
|
||||
class MetronomeView : public View {
|
||||
public:
|
||||
MetronomeView(NavigationView& nav);
|
||||
~MetronomeView();
|
||||
MetronomeView(const MetronomeView& other) = delete;
|
||||
MetronomeView& operator=(const MetronomeView& other) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Metronome"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void beep_accent_beat(); // e.g. 3 of 3/4 beat
|
||||
void beep_unaccent_beat(); // e.g. 4 of 3/4 beat
|
||||
void stop_play();
|
||||
void play();
|
||||
// void paint(Painter& painter) override;
|
||||
|
||||
Thread* thread{nullptr};
|
||||
bool should_exit{false};
|
||||
static msg_t static_fn(void* arg);
|
||||
void run();
|
||||
|
||||
bool playing_{false};
|
||||
uint32_t current_beat_{0};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "BPM:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 2 * 16}, "Accent Beep Tune:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "Unaccent Beep Tune:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "Rhythm:", Theme::getInstance()->fg_light->foreground},
|
||||
{{(sizeof("Rhythm:") + 1) * 8 + 4 * 8, 4 * 16}, "/", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Beep Flash Duration:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_bpm{
|
||||
{(sizeof("BPM:") + 1) * 8, 1 * 16},
|
||||
4,
|
||||
{1, 1000},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_rythm_unaccent_time{// e.g. 3 in 3/4 beat
|
||||
{(sizeof("Rhythm:") + 1) * 8, 4 * 16},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_rythm_accent_time{// e.g. 4 in 3/4 beat
|
||||
{(sizeof("Rhythm:") + 1) * 8 + 5 * 8, 4 * 16},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_beep_flash_duration{
|
||||
{(sizeof("Beep Flash Duration:") + 1) * 8, 5 * 16},
|
||||
3,
|
||||
{10, 999},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_accent_beep_tune{
|
||||
{(sizeof("Accent Beep Tune:") + 1) * 8, 2 * 16},
|
||||
5,
|
||||
{380, 24000},
|
||||
20,
|
||||
' '};
|
||||
|
||||
NumberField field_unaccent_beep_tune{
|
||||
{(sizeof("Unaccent Beep Tune:") + 1) * 8, 3 * 16},
|
||||
5,
|
||||
{380, 24000},
|
||||
20,
|
||||
' '};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{(sizeof("Volume:") + 1) * 8, 6 * 16}};
|
||||
|
||||
NewButton button_play_stop{
|
||||
{0 * 16, 16 * 16, screen_width, screen_height - 16 * 16},
|
||||
{},
|
||||
&bitmap_icon_replay,
|
||||
Theme::getInstance()->fg_red->foreground};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 16, 8 * 16, screen_width, screen_height - 14 * 16}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::metronome
|
||||
|
||||
#endif /*__UI_METRONOME_H__*/
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_morse_tx.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_tones */ {'P', 'T', 'O', 'N'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+2
-3
@@ -232,9 +232,8 @@ MorseView::MorseView(
|
||||
set_foxhunt(foxhunt_code);
|
||||
};
|
||||
|
||||
options_modulation.on_change = [this](size_t i, int32_t value) {
|
||||
(void)i; // avoid unused warning
|
||||
mode_cw = (bool)value;
|
||||
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
mode_cw = (bool)v;
|
||||
};
|
||||
|
||||
options_loop.on_change = [this](size_t i, uint32_t value) {
|
||||
|
||||
+7
-2
@@ -41,6 +41,11 @@ using namespace morse;
|
||||
|
||||
namespace ui::external_app::morse_tx {
|
||||
|
||||
enum Modulation {
|
||||
FM = 0,
|
||||
CW = 1
|
||||
};
|
||||
|
||||
class MorseView : public View {
|
||||
public:
|
||||
MorseView(NavigationView& nav);
|
||||
@@ -137,8 +142,8 @@ class MorseView : public View {
|
||||
OptionsField options_modulation{
|
||||
{15 * 8, 10 * 8},
|
||||
2,
|
||||
{{"CW", true},
|
||||
{"FM", false}}};
|
||||
{{"CW", Modulation::CW},
|
||||
{"FM", Modulation::FM}}};
|
||||
|
||||
OptionsField options_loop{
|
||||
{9 * 8, 12 * 8},
|
||||
|
||||
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_nrf_rx.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_nrf_rx */ {'P', 'N', 'R', 'R'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_ook_editor.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -57,6 +57,7 @@ void OOKEditorAppView::start_tx() {
|
||||
|
||||
// `stop_tx` method: Stops the transmission and resets the progress bar.
|
||||
void OOKEditorAppView::stop_tx() {
|
||||
// TODO: model stopped but message still spamming.
|
||||
is_transmitting = false; // set transmitting flag
|
||||
stop_ook_file_tx(); // stop transmission
|
||||
progressbar.set_value(0); // Reset progress bar to 0
|
||||
@@ -82,7 +83,7 @@ void OOKEditorAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
|
||||
// `on_tx_progress` method: Updates the progress bar based on transmission progress.
|
||||
void OOKEditorAppView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
progressbar.set_value(progress); // Update progress bar value
|
||||
if (is_transmitting) progressbar.set_value(progress); // Update progress bar value
|
||||
if (done) {
|
||||
stop_tx(); // Stop transmission when progress reaches maximum
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_ookbrute.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_protoview.application_information"), u
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_protoview */ {'P', 'P', 'V', 'W'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_remote.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::HOME,
|
||||
/*.desired_menu_position = */ 4,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_replay */ {'P', 'R', 'E', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ class RemoteIcons {
|
||||
&bitmap_icon_sonde,
|
||||
&bitmap_icon_stealth,
|
||||
&bitmap_icon_tetra,
|
||||
&bitmap_icon_temperature};
|
||||
&bitmap_icon_peripherals_details};
|
||||
};
|
||||
|
||||
// TODO: Use RGB colors instead?
|
||||
|
||||
+13
-10
@@ -16,12 +16,13 @@ void initialize_app(NavigationView& nav) {
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_shoppingcart_lock.application_information"), used)) application_information_t _application_information_shoppingcart_lock = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::shoppingcart_lock::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"Cart Lock",
|
||||
{
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::shoppingcart_lock::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Cart Lock",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -55,9 +56,11 @@ __attribute__((section(".external_app.app_shoppingcart_lock.application_informat
|
||||
0x7E,
|
||||
0x00,
|
||||
},
|
||||
ui::Color::red().v,
|
||||
app_location_t::UTILITIES,
|
||||
{'P', 'A', 'T', 'X'},
|
||||
0x00000000,
|
||||
/*.icon_color = */ ui::Color::red().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'T', 'X'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_spainter.application_information"), us
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_spainter */ {'P', 'S', 'P', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_sstvtx.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_sstvtx */ {'P', 'S', 'T', 'X'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_tpmsrx.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_tpms */ {'P', 'T', 'P', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_tuner.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::tuner {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<TunerView>();
|
||||
}
|
||||
} // namespace ui::external_app::tuner
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_tuner.application_information"), used)) application_information_t _application_information_tuner = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::tuner::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Tuner",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x22,
|
||||
0x44,
|
||||
0x21,
|
||||
0x84,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x25,
|
||||
0xA4,
|
||||
0x25,
|
||||
0xA4,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x61,
|
||||
0x86,
|
||||
0xC2,
|
||||
0x43,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* copyleft 2024 sommermorgentraum
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_tuner.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::tuner {
|
||||
|
||||
TunerView::TunerView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_volume,
|
||||
&options_instrument,
|
||||
&options_note,
|
||||
&button_play_stop,
|
||||
&text_note_frequency,
|
||||
&text_note_octave_shift,
|
||||
});
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
|
||||
options_instrument.on_change = [this](size_t, int32_t value) {
|
||||
const Instrument* selected_instrument = nullptr;
|
||||
|
||||
switch (value) {
|
||||
case 0:
|
||||
selected_instrument = &guitar;
|
||||
break;
|
||||
case 1:
|
||||
selected_instrument = &violin;
|
||||
break;
|
||||
case 2:
|
||||
selected_instrument = &pitch_fork;
|
||||
break;
|
||||
}
|
||||
|
||||
if (selected_instrument) {
|
||||
update_notes_for_instrument(*selected_instrument);
|
||||
}
|
||||
|
||||
update_current_note();
|
||||
};
|
||||
|
||||
options_note.on_change = [this](size_t, int32_t index) {
|
||||
const Instrument* current_instrument = nullptr;
|
||||
switch (options_instrument.selected_index_value()) {
|
||||
case 0:
|
||||
current_instrument = &guitar;
|
||||
break;
|
||||
case 1:
|
||||
current_instrument = &violin;
|
||||
break;
|
||||
case 2:
|
||||
current_instrument = &pitch_fork;
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_instrument) {
|
||||
auto it = current_instrument->notes.begin();
|
||||
std::advance(it, index);
|
||||
if (it != current_instrument->notes.end()) {
|
||||
current_note_frequency = it->second.frequency;
|
||||
current_note_sample_rate = it->second.sample_rate;
|
||||
current_note_octave_shift = it->second.octave_shift;
|
||||
}
|
||||
}
|
||||
|
||||
update_current_note();
|
||||
};
|
||||
|
||||
button_play_stop.on_select = [this]() {
|
||||
if (current_note_playing) {
|
||||
stop_play();
|
||||
} else {
|
||||
play_change_note();
|
||||
}
|
||||
};
|
||||
|
||||
options_instrument.set_selected_index(0);
|
||||
update_notes_for_instrument(guitar);
|
||||
update_current_note();
|
||||
|
||||
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
|
||||
field_volume.set_value(99);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
TunerView::~TunerView() {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
audio::output::stop();
|
||||
}
|
||||
|
||||
void TunerView::focus() {
|
||||
options_instrument.focus();
|
||||
}
|
||||
|
||||
void TunerView::update_notes_for_instrument(const Instrument& instrument) {
|
||||
std::vector<OptionsField::option_t> note_options;
|
||||
size_t index = 0;
|
||||
|
||||
for (const auto& note_pair : instrument.notes) {
|
||||
note_options.emplace_back(OptionsField::option_t{
|
||||
note_pair.first,
|
||||
index++});
|
||||
}
|
||||
|
||||
options_note.set_options(note_options);
|
||||
if (!note_options.empty()) {
|
||||
options_note.set_selected_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
void TunerView::update_current_note() {
|
||||
const Instrument* current_instrument = nullptr;
|
||||
|
||||
switch (options_instrument.selected_index_value()) {
|
||||
case 0:
|
||||
current_instrument = &guitar;
|
||||
break;
|
||||
case 1:
|
||||
current_instrument = &violin;
|
||||
break;
|
||||
case 2:
|
||||
current_instrument = &pitch_fork;
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_instrument) {
|
||||
std::string note_name = options_note.selected_index_name();
|
||||
|
||||
// map
|
||||
auto note_it = current_instrument->notes.find(note_name);
|
||||
if (note_it != current_instrument->notes.end()) {
|
||||
current_note_frequency = note_it->second.frequency;
|
||||
current_note_sample_rate = note_it->second.sample_rate;
|
||||
current_note_octave_shift = note_it->second.octave_shift;
|
||||
|
||||
text_note_frequency.set(std::to_string(current_note_frequency));
|
||||
text_note_octave_shift.set(std::to_string(current_note_octave_shift));
|
||||
|
||||
set_dirty();
|
||||
|
||||
if (current_note_playing) {
|
||||
play_change_note();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunerView::stop_play() {
|
||||
baseband::request_beep_stop();
|
||||
current_note_playing = false;
|
||||
button_play_stop.set_bitmap(&bitmap_icon_replay);
|
||||
}
|
||||
|
||||
void TunerView::play_change_note() {
|
||||
if (current_note_playing) {
|
||||
baseband::request_beep_stop();
|
||||
audio::set_rate(current_note_sample_rate);
|
||||
baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
|
||||
} else {
|
||||
audio::set_rate(current_note_sample_rate);
|
||||
baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
|
||||
}
|
||||
current_note_playing = true;
|
||||
button_play_stop.set_bitmap(&bitmap_icon_sleep);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
uint32_t TunerView::protected_sample_rate(audio::Rate r) {
|
||||
switch (r) {
|
||||
case audio::Rate::Hz_12000:
|
||||
return 12000;
|
||||
case audio::Rate::Hz_24000:
|
||||
return 24000;
|
||||
case audio::Rate::Hz_48000:
|
||||
return 48000;
|
||||
default:
|
||||
return 24000;
|
||||
}
|
||||
}
|
||||
|
||||
void TunerView::paint(Painter& painter) {
|
||||
View::paint(painter);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{screen_width / 4, 8 * 16, screen_width / 2, 6 * 16},
|
||||
Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
if (!current_note_playing) return;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{screen_width / 4, 10 * 16, 2, 2 * 16},
|
||||
Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{screen_width / 4, 12 * 16, screen_width / 4 * 2 + 2, 2},
|
||||
Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{(screen_width / 4) * 3, 10 * 16, 2, 2 * 16},
|
||||
Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
painter.draw_string(
|
||||
{screen_width / 4 - 2 * 8, 8 * 16},
|
||||
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
|
||||
std::to_string(current_note_frequency));
|
||||
|
||||
int16_t real_frequency = current_note_frequency;
|
||||
if (current_note_octave_shift > 0) {
|
||||
for (int i = 0; i < current_note_octave_shift; i++) {
|
||||
real_frequency *= 2;
|
||||
}
|
||||
} else if (current_note_octave_shift < 0) {
|
||||
for (int i = 0; i > current_note_octave_shift; i--) {
|
||||
real_frequency /= 2;
|
||||
}
|
||||
}
|
||||
painter.draw_string({(screen_width / 4) * 3 - 2 * 8, 8 * 16},
|
||||
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
|
||||
std::to_string(real_frequency));
|
||||
|
||||
painter.draw_string({screen_width / 2 - 3 * 16, 13 * 16},
|
||||
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
|
||||
std::to_string(current_note_octave_shift) + " * 8ev");
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::tuner
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* copyleft 2024 sommermorgentraum
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_TUNER_H__
|
||||
#define __UI_TUNER_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
namespace ui::external_app::tuner {
|
||||
|
||||
struct Instrument {
|
||||
std::string name;
|
||||
struct NoteInfo {
|
||||
uint16_t frequency;
|
||||
audio::Rate sample_rate; // tune samplerate to allow for freqs
|
||||
int8_t octave_shift;
|
||||
// PP hardware can't handle some extremely low/high frequencies, this indicates how much the freq that were played is shifted up/down,
|
||||
// for example, if shift is -2 and note is A3, it means the real string should play A5 but PP's speaker plays A3
|
||||
};
|
||||
std::map<std::string, NoteInfo> notes; // this is for fast looking : O(log(n)) , usage `auto note = guitar.notes["A4"];`
|
||||
};
|
||||
|
||||
class TunerView : public View {
|
||||
public:
|
||||
TunerView(NavigationView& nav);
|
||||
~TunerView();
|
||||
TunerView(const TunerView& other) = delete;
|
||||
TunerView& operator=(const TunerView& other) = delete;
|
||||
|
||||
void focus() override;
|
||||
void update_audio_beep();
|
||||
|
||||
std::string title() const override { return "Tuner"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
bool beep{false};
|
||||
|
||||
void update_notes_for_instrument(const Instrument& instrument);
|
||||
void play_change_note();
|
||||
void stop_play();
|
||||
void update_current_note();
|
||||
uint32_t protected_sample_rate(audio::Rate r);
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
uint16_t current_note_frequency{440};
|
||||
audio::Rate current_note_sample_rate{audio::Rate::Hz_12000};
|
||||
int8_t current_note_octave_shift{0};
|
||||
bool current_note_playing{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Instrument:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 2 * 16}, "Note:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "Note Frequency:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "Note Octave Shift:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_note_frequency{
|
||||
{(sizeof("Note Frequency:") + 1) * 8, 3 * 16, screen_width - (sizeof("Note Frequency:") + 1) * 8, 16},
|
||||
"",
|
||||
};
|
||||
|
||||
Text text_note_octave_shift{
|
||||
{(sizeof("Note Octave Shift:") + 1) * 8, 4 * 16, screen_width - (sizeof("Note Octave Shift:") + 1) * 8, 16},
|
||||
"",
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{(sizeof("Volume:") + 1) * 8, 5 * 16}};
|
||||
|
||||
// TODO: load list runtime
|
||||
OptionsField options_instrument{
|
||||
{(sizeof("Instrument:") + 1) * 8, 1 * 16},
|
||||
screen_width - (sizeof("Instrument:") + 1) * 8,
|
||||
{{"Guitar", 0},
|
||||
{"Violin", 1},
|
||||
{"Pitch Fork", 2}}};
|
||||
|
||||
OptionsField options_note{
|
||||
{(sizeof("Note:") + 1) * 8, 2 * 16},
|
||||
screen_width - (sizeof("Note:") + 1) * 8,
|
||||
{}};
|
||||
|
||||
NewButton button_play_stop{
|
||||
{0 * 8, 16 * 16, screen_width, screen_height - 16 * 16},
|
||||
{},
|
||||
&bitmap_icon_replay,
|
||||
Theme::getInstance()->fg_red->foreground};
|
||||
|
||||
// TODO: load from file DB
|
||||
const Instrument guitar = {
|
||||
.name = "Folk Guitar",
|
||||
.notes = {
|
||||
{"E2", {165, audio::Rate::Hz_12000, -1}}, // actual: E2, PP speaker: E3
|
||||
{"A2", {110, audio::Rate::Hz_12000, 0}},
|
||||
{"D3", {147, audio::Rate::Hz_12000, 0}},
|
||||
{"G3", {196, audio::Rate::Hz_24000, 0}},
|
||||
{"B3", {247, audio::Rate::Hz_24000, 0}},
|
||||
{"E4", {330, audio::Rate::Hz_24000, 0}}}};
|
||||
|
||||
const Instrument violin = {
|
||||
.name = "Violin 440 Standard, 12ET",
|
||||
.notes = {
|
||||
{"G3", {196, audio::Rate::Hz_12000, 0}},
|
||||
{"D4", {294, audio::Rate::Hz_24000, 0}},
|
||||
{"A4", {440, audio::Rate::Hz_48000, 0}},
|
||||
{"E5", {659, audio::Rate::Hz_48000, 0}}}};
|
||||
|
||||
const Instrument pitch_fork = {// freq copied from flipper app
|
||||
.name = "Pitch Fork",
|
||||
.notes = {
|
||||
{"12ET A4", {440, audio::Rate::Hz_48000, 0}},
|
||||
{"Sarti's A4", {436, audio::Rate::Hz_48000, 0}},
|
||||
{"1858 A4", {435, audio::Rate::Hz_48000, 0}},
|
||||
{"Verdi's A4", {432, audio::Rate::Hz_48000, 0}}}};
|
||||
|
||||
std::map<std::string, Instrument> instruments{
|
||||
{"Guitar", guitar},
|
||||
{"Violin", violin},
|
||||
{"Pitch Fork", pitch_fork}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::tuner
|
||||
|
||||
#endif /*__UI_TUNER_H__*/
|
||||
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_wardrivemap.application_information"),
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
@@ -28,8 +28,11 @@
|
||||
using option_t = ui::OptionsField::option_t;
|
||||
using options_t = ui::OptionsField::options_t;
|
||||
|
||||
extern options_t freqman_steps;
|
||||
extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
|
||||
using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_steps;
|
||||
extern const option_db_t* find_by_index(const options_db_t& options, freqman_index_t index);
|
||||
|
||||
/* Option value lookup. */
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step) {
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace fs = std::filesystem;
|
||||
const std::filesystem::path freqman_extension{u".TXT"};
|
||||
|
||||
// NB: Don't include UI headers to keep this code unit testable.
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using option_t = std::pair<std::string_view, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
options_t freqman_modulations = {
|
||||
@@ -194,27 +194,27 @@ bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
|
||||
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->first;
|
||||
return (std::string)opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth) {
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->first;
|
||||
return (std::string)opt->first;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_step_string(freqman_index_t step) {
|
||||
if (auto opt = find_by_index(freqman_steps, step))
|
||||
return opt->first;
|
||||
return (std::string)opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_step_string_short(freqman_index_t step) {
|
||||
if (auto opt = find_by_index(freqman_steps_short, step))
|
||||
return opt->first;
|
||||
return (std::string)opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ float tone_key_frequency(tone_index index) {
|
||||
std::string tone_key_string(tone_index index) {
|
||||
if (index < 0 || (unsigned)index >= tone_keys.size())
|
||||
return std::string("");
|
||||
return tone_keys[index].first;
|
||||
return (std::string)tone_keys[index].first;
|
||||
}
|
||||
|
||||
// Return string showing frequency only from specific table index
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace tonekey {
|
||||
@@ -35,7 +36,7 @@ namespace tonekey {
|
||||
#define F2Ix100(x) (int32_t)(x * 100.0 + 0.5) // add 0.5f to round vs truncate during FP->int conversion
|
||||
|
||||
using tone_index = int32_t;
|
||||
using tone_key_t = std::vector<std::pair<std::string, uint32_t>>;
|
||||
using tone_key_t = std::vector<std::pair<std::string_view, uint32_t>>;
|
||||
|
||||
extern const tone_key_t tone_keys;
|
||||
|
||||
|
||||
@@ -118,30 +118,36 @@ void BtnGridView::clear() {
|
||||
menu_item_views.clear();
|
||||
}
|
||||
|
||||
void BtnGridView::add_items(std::initializer_list<GridItem> new_items) {
|
||||
void BtnGridView::add_items(std::initializer_list<GridItem> new_items, bool inhibit_update) {
|
||||
for (auto item : new_items) {
|
||||
if (!blacklisted_app(item))
|
||||
menu_items.push_back(item);
|
||||
}
|
||||
|
||||
update_items();
|
||||
}
|
||||
|
||||
void BtnGridView::add_item(GridItem new_item) {
|
||||
if (!blacklisted_app(new_item)) {
|
||||
menu_items.push_back(new_item);
|
||||
if (!inhibit_update) {
|
||||
update_items();
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::insert_item(GridItem new_item, uint8_t position) {
|
||||
void BtnGridView::add_item(const GridItem& new_item, bool inhibit_update) {
|
||||
if (!blacklisted_app(new_item)) {
|
||||
menu_items.push_back(new_item);
|
||||
if (!inhibit_update) {
|
||||
update_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::insert_item(const GridItem& new_item, size_t position, bool inhibit_update) {
|
||||
if (!blacklisted_app(new_item)) {
|
||||
if (position < menu_items.size()) {
|
||||
auto pos_iter = menu_items.begin() + position;
|
||||
menu_items.insert(pos_iter, new_item);
|
||||
update_items();
|
||||
} else {
|
||||
menu_items.push_back(new_item);
|
||||
}
|
||||
|
||||
if (!inhibit_update) {
|
||||
update_items();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ class BtnGridView : public View {
|
||||
|
||||
~BtnGridView();
|
||||
|
||||
void add_items(std::initializer_list<GridItem> new_items);
|
||||
void add_item(GridItem new_item);
|
||||
void insert_item(GridItem new_item, uint8_t position);
|
||||
void add_items(std::initializer_list<GridItem> new_items, bool inhibit_update = false);
|
||||
void add_item(const GridItem& new_item, bool inhibit_update = false);
|
||||
void insert_item(const GridItem& new_item, size_t position, bool inhibit_update = false);
|
||||
void set_max_rows(int rows);
|
||||
int rows();
|
||||
void clear();
|
||||
@@ -81,12 +81,13 @@ class BtnGridView : public View {
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool blacklisted_app(GridItem new_item);
|
||||
|
||||
void update_items();
|
||||
|
||||
protected:
|
||||
virtual void on_populate() = 0;
|
||||
|
||||
private:
|
||||
int rows_{3};
|
||||
void update_items();
|
||||
void on_tick_second();
|
||||
|
||||
bool keep_highlight{false};
|
||||
|
||||
@@ -344,7 +344,7 @@ class GeoMapView : public View {
|
||||
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
|
||||
|
||||
Button button_ok{
|
||||
{20 * 8, 8, 8 * 8, 2 * 16},
|
||||
{screen_width - 15 * 8, 0, 15 * 8, 1 * 16},
|
||||
"OK"};
|
||||
};
|
||||
|
||||
|
||||
@@ -100,8 +100,19 @@ void RSSI::paint(Painter& painter) {
|
||||
const Rect r5{r.left() + peak - 3, r.top(), 3, r.height()};
|
||||
painter.fill_rectangle(
|
||||
r5,
|
||||
Theme::getInstance()->fg_green->foreground);
|
||||
Theme::getInstance()->fg_orange->foreground);
|
||||
}
|
||||
|
||||
// dB - x
|
||||
constexpr int db_min = -80;
|
||||
constexpr int db_max = 10;
|
||||
constexpr int db_delta = db_max - db_min;
|
||||
const range_t<int> x_db_range{0, r.width() - 1};
|
||||
const int16_t x_db = x_db_range.clip((db_ - db_min) * r.width() / db_delta);
|
||||
|
||||
const Rect r_db{r.left() + x_db, r.top(), 1, r.height()};
|
||||
|
||||
if (db_) painter.fill_rectangle(r_db, Color::green());
|
||||
} else {
|
||||
// vertical bottom to top level meters
|
||||
const range_t<int> y_avg_range{0, r.height() - 1};
|
||||
@@ -115,7 +126,7 @@ void RSSI::paint(Painter& painter) {
|
||||
|
||||
// y_min
|
||||
const Rect r0{r.left(), r.bottom() - y_min, r.width(), y_min};
|
||||
painter.fill_rectangle(
|
||||
painter.fill_rectangle( // TODO: the blue plot is broken in vertical bars, not from the dB PR (#2403)
|
||||
r0,
|
||||
Color::blue());
|
||||
|
||||
@@ -149,8 +160,18 @@ void RSSI::paint(Painter& painter) {
|
||||
const Rect r5{r.left(), r.bottom() - peak - 3, r.width(), 3};
|
||||
painter.fill_rectangle(
|
||||
r5,
|
||||
Color::green());
|
||||
Color::orange());
|
||||
}
|
||||
|
||||
// dB - y
|
||||
constexpr int db_min = -80;
|
||||
constexpr int db_max = 10;
|
||||
constexpr int db_delta = db_max - db_min;
|
||||
const range_t<int> y_db_range{0, r.height() - 1};
|
||||
const int16_t y_db = y_db_range.clip((db_ - db_min) * r.height() / db_delta);
|
||||
|
||||
const Rect r_db{r.left(), r.bottom() - y_db, r.width(), 3};
|
||||
if (db_) painter.fill_rectangle(r_db, Color::green());
|
||||
}
|
||||
if (pitch_rssi_enabled) {
|
||||
baseband::set_pitch_rssi((avg_ - raw_min) * 2000 / raw_delta, true);
|
||||
@@ -159,7 +180,7 @@ void RSSI::paint(Painter& painter) {
|
||||
const Rect r6{r.left(), r.top(), r.width(), r.height()};
|
||||
painter.draw_rectangle(
|
||||
r6,
|
||||
Color::white());
|
||||
Color::white()); // TODO this and all the following Color struct call should satisfy the new "theme" system ref
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,4 +521,8 @@ bool RSSI::on_touch(const TouchEvent event) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RSSI::set_db(int16_t db) {
|
||||
db_ = db;
|
||||
}
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -61,6 +61,7 @@ class RSSI : public Widget {
|
||||
void on_focus() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
void set_db(int16_t db);
|
||||
|
||||
private:
|
||||
int8_t min_ = 0;
|
||||
@@ -68,6 +69,7 @@ class RSSI : public Widget {
|
||||
int8_t max_ = 0;
|
||||
int8_t peak_ = 0;
|
||||
size_t peak_duration_ = 0;
|
||||
int16_t db_ = 0;
|
||||
bool instant_exec_{false};
|
||||
|
||||
bool pitch_rssi_enabled = false;
|
||||
|
||||
@@ -34,7 +34,7 @@ void tone_keys_populate(OptionsField& field) {
|
||||
for (size_t c = 0; c < tone_keys.size(); c++) {
|
||||
auto f = tone_keys[c].second;
|
||||
if ((c != 0) && (f < 1000 * 100))
|
||||
tone_name = "CTCSS " + fx100_string(f) + " #" + tone_keys[c].first;
|
||||
tone_name = "CTCSS " + fx100_string(f) + " #" + (std::string)tone_keys[c].first;
|
||||
else
|
||||
tone_name = tone_keys[c].first;
|
||||
|
||||
|
||||
@@ -97,10 +97,10 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ std::vector<GridItem> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
/* static */ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
bitmaps.clear();
|
||||
|
||||
std::vector<GridItem> external_apps;
|
||||
std::vector<GridItemEx> external_apps;
|
||||
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace ui {
|
||||
if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
GridItem gridItem = {};
|
||||
GridItemEx gridItem = {};
|
||||
gridItem.text = reinterpret_cast<char*>(&appInfo->app_name[0]);
|
||||
|
||||
gridItem.color = Color((uint16_t)appInfo->icon_color);
|
||||
@@ -150,6 +150,8 @@ namespace ui {
|
||||
nav.display_modal("Error", "Unable to download app.");
|
||||
};
|
||||
|
||||
gridItem.desired_position = -1; // TODO: Where should we put the module's app icon? First? Last? Also configurable?
|
||||
|
||||
external_apps.push_back(gridItem);
|
||||
}
|
||||
}
|
||||
@@ -180,7 +182,7 @@ namespace ui {
|
||||
|
||||
bool versionMatches = VERSION_MD5 == application_information.app_version;
|
||||
|
||||
GridItem gridItem = {};
|
||||
GridItemEx gridItem = {};
|
||||
gridItem.text = reinterpret_cast<char*>(&application_information.app_name[0]);
|
||||
|
||||
if (versionMatches) {
|
||||
@@ -195,6 +197,8 @@ namespace ui {
|
||||
nav.display_modal("Error", "The .ppma file in your " + apps_dir.string() + "\nfolder can't be read. Please\nupdate your SD Card content.");
|
||||
}
|
||||
};
|
||||
|
||||
gridItem.desired_position = application_information.desired_menu_position;
|
||||
} else {
|
||||
gridItem.color = Theme::getInstance()->fg_light->foreground;
|
||||
|
||||
@@ -203,6 +207,8 @@ namespace ui {
|
||||
gridItem.on_select = [&nav]() {
|
||||
nav.display_modal("Error", "The .ppma file in your " + apps_dir.string() + "\nfolder is outdated. Please\nupdate your SD Card content.");
|
||||
};
|
||||
|
||||
gridItem.desired_position = application_information.desired_menu_position;
|
||||
}
|
||||
|
||||
external_apps.push_back(gridItem);
|
||||
@@ -228,7 +234,7 @@ namespace ui {
|
||||
if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
GridItem gridItem = {};
|
||||
GridItemEx gridItem = {};
|
||||
gridItem.text = reinterpret_cast<char*>(&application_information.app_name[0]);
|
||||
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
@@ -243,6 +249,8 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
|
||||
gridItem.desired_position = -1; // No desired position support for standalone apps yet
|
||||
|
||||
external_apps.push_back(gridItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,11 @@ class DynamicBitmap {
|
||||
|
||||
class ExternalItemsMenuLoader {
|
||||
public:
|
||||
static std::vector<GridItem> load_external_items(app_location_t, NavigationView&);
|
||||
struct GridItemEx : GridItem {
|
||||
int32_t desired_position;
|
||||
};
|
||||
|
||||
static std::vector<GridItemEx> load_external_items(app_location_t, NavigationView&);
|
||||
ExternalItemsMenuLoader() = delete;
|
||||
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
|
||||
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
|
||||
|
||||
@@ -174,7 +174,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_temperature, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
|
||||
};
|
||||
|
||||
@@ -330,6 +330,7 @@ SystemStatusView::SystemStatusView(
|
||||
|
||||
audio::output::stop();
|
||||
audio::output::update_audio_mute();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -752,16 +753,19 @@ static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc)
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, &app]() {
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0); //if i navigate away from any menu, turn off autoscan
|
||||
nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }});
|
||||
nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }},
|
||||
true);
|
||||
}
|
||||
};
|
||||
|
||||
grid.update_items();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView& grid) {
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos) {
|
||||
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
|
||||
if (externalItems.empty()) {
|
||||
grid.insert_item({"Notice!",
|
||||
grid.insert_item({"ExtAppErr",
|
||||
Theme::getInstance()->error_dark->foreground,
|
||||
nullptr,
|
||||
[&nav]() {
|
||||
@@ -771,28 +775,47 @@ void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView&
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to " + apps_dir.string() + " folder of SD card.");
|
||||
}},
|
||||
pmem::show_gui_return_icon() ? 1 : 0);
|
||||
error_tile_pos);
|
||||
} else {
|
||||
std::sort(externalItems.begin(), externalItems.end(), [](const auto &a, const auto &b)
|
||||
{
|
||||
return a.desired_position < b.desired_position;
|
||||
});
|
||||
|
||||
for (auto const& gridItem : externalItems) {
|
||||
grid.add_item(gridItem);
|
||||
if (gridItem.desired_position < 0) {
|
||||
grid.add_item(gridItem, true);
|
||||
} else {
|
||||
grid.insert_item(gridItem, gridItem.desired_position, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
grid.update_items();
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
bool verify_sdcard_format() {
|
||||
FATFS* fs = &sd_card::fs;
|
||||
return (fs->fs_type == FS_FAT32 || fs->fs_type == FS_EXFAT) || !(sd_card::status() == sd_card::Status::Mounted);
|
||||
/* ^ to satisfy those users that not use an sd*/
|
||||
}
|
||||
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav)
|
||||
: nav_(nav) {}
|
||||
|
||||
void ReceiversMenuView::on_populate() {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, RX);
|
||||
|
||||
addExternalItems(nav_, app_location_t::RX, *this);
|
||||
add_external_items(nav_, app_location_t::RX, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* TransmittersMenuView **************************************************/
|
||||
@@ -801,13 +824,14 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav)
|
||||
: nav_(nav) {}
|
||||
|
||||
void TransmittersMenuView::on_populate() {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, TX);
|
||||
|
||||
addExternalItems(nav_, app_location_t::TX, *this);
|
||||
add_external_items(nav_, app_location_t::TX, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* UtilitiesMenuView *****************************************************/
|
||||
@@ -818,13 +842,14 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav)
|
||||
}
|
||||
|
||||
void UtilitiesMenuView::on_populate() {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_apps(nav_, *this, UTILITIES);
|
||||
|
||||
addExternalItems(nav_, app_location_t::UTILITIES, *this);
|
||||
add_external_items(nav_, app_location_t::UTILITIES, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* SystemMenuView ********************************************************/
|
||||
@@ -848,8 +873,13 @@ SystemMenuView::SystemMenuView(NavigationView& nav)
|
||||
}
|
||||
|
||||
void SystemMenuView::on_populate() {
|
||||
if (!verify_sdcard_format()) {
|
||||
add_item({"SDCard Error", Theme::getInstance()->error_dark->foreground, nullptr, [this]() {
|
||||
nav_.display_modal("Error", "SD Card is not exFAT/FAT32,\nformat to exFAT or FAT32 on PC");
|
||||
}});
|
||||
}
|
||||
add_apps(nav_, *this, HOME);
|
||||
addExternalItems(nav_, app_location_t::HOME, *this);
|
||||
add_external_items(nav_, app_location_t::HOME, *this, 0);
|
||||
add_item({"HackRF", Theme::getInstance()->fg_cyan->foreground, &bitmap_icon_hackrf, [this]() { hackrf_mode(nav_); }});
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ class SystemStatusView : public View {
|
||||
static constexpr auto default_title = "";
|
||||
bool batt_was_inited = false; // if the battery was off on tart, but later turned on.
|
||||
bool batt_info_up = false; // to prevent show multiple batt info dialog
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
@@ -322,7 +323,7 @@ class InformationView : public View {
|
||||
|
||||
Rectangle backdrop{
|
||||
{0, 0 * 16, 240, 16},
|
||||
{33, 33, 33}};
|
||||
Theme::getInstance()->bg_darker->background};
|
||||
|
||||
Text version{
|
||||
{2, 0, 11 * 8, 16},
|
||||
|
||||
@@ -240,21 +240,16 @@ namespace ui {
|
||||
|
||||
SDCardDebugView::SDCardDebugView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&text_csd_title,
|
||||
&labels,
|
||||
&text_format,
|
||||
&text_csd_value_3,
|
||||
&text_csd_value_2,
|
||||
&text_csd_value_1,
|
||||
&text_csd_value_0,
|
||||
&text_bus_width_title,
|
||||
&text_bus_width_value,
|
||||
&text_card_type_title,
|
||||
&text_card_type_value,
|
||||
&text_block_size_title,
|
||||
&text_block_size_value,
|
||||
&text_block_count_title,
|
||||
&text_block_count_value,
|
||||
&text_capacity_title,
|
||||
&text_capacity_value,
|
||||
&text_test_write_time_title,
|
||||
&text_test_write_time_value,
|
||||
@@ -374,6 +369,14 @@ void SDCardDebugView::on_status(const sd_card::Status) {
|
||||
text_csd_value_1.set(to_string_hex(csd[1], 8));
|
||||
text_csd_value_0.set(to_string_hex(csd[0], 8));
|
||||
|
||||
text_format.set(fetch_sdcard_format());
|
||||
if (fetch_sdcard_format().find("FAT32") != std::string::npos) {
|
||||
// to satisfy the intendent style in this app, the text contains padding space, thus can't use ==
|
||||
text_format.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_format.set_style(Theme::getInstance()->error_dark);
|
||||
}
|
||||
|
||||
BlockDeviceInfo block_device_info;
|
||||
if (sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS) {
|
||||
text_block_size_value.set(to_string_dec_uint(block_device_info.blk_size, 5));
|
||||
@@ -447,4 +450,38 @@ void SDCardDebugView::on_test() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string SDCardDebugView::fetch_sdcard_format() {
|
||||
const size_t max_len = sizeof("Undefined: 255") + 1;
|
||||
|
||||
auto padding = [max_len](std::string s) {
|
||||
if (s.length() < max_len) {
|
||||
return std::string(max_len - s.length(), ' ') + s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
FATFS* fs = &sd_card::fs;
|
||||
std::string resault;
|
||||
|
||||
switch (fs->fs_type) {
|
||||
case FS_FAT12:
|
||||
resault = "FAT12";
|
||||
break;
|
||||
case FS_FAT16:
|
||||
resault = "FAT16";
|
||||
break;
|
||||
case FS_FAT32:
|
||||
resault = "FAT32";
|
||||
break;
|
||||
case FS_EXFAT:
|
||||
resault = "ExFAT";
|
||||
break;
|
||||
default:
|
||||
resault = "Undefined: " + to_string_dec_uint(fs->fs_type, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return padding(resault);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -45,15 +45,23 @@ class SDCardDebugView : public View {
|
||||
|
||||
void on_status(const sd_card::Status status);
|
||||
void on_test();
|
||||
std::string fetch_sdcard_format();
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Format", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "CSD", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Bus width", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Card type", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "Block size", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 9 * 16}, "Block count", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 10 * 16}, "Capacity", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Bus width", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
Text text_title{
|
||||
{(240 - (7 * 8)) / 2, 1 * 16, (7 * 8), 16},
|
||||
"SD Card",
|
||||
};
|
||||
|
||||
Text text_csd_title{
|
||||
{0, 3 * 16, (8 * 8), 16},
|
||||
"CSD",
|
||||
Text text_format{
|
||||
{240 - ((sizeof("Undefined: 255") + 1) * 8), 1 * 16, (sizeof("Undefined: 255") + 1) * 8, 16},
|
||||
"Unknown",
|
||||
};
|
||||
|
||||
Text text_csd_value_3{
|
||||
@@ -78,11 +86,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t bus_width_characters = 1;
|
||||
|
||||
Text text_bus_width_title{
|
||||
{0, 5 * 16, (9 * 8), 16},
|
||||
"Bus width",
|
||||
};
|
||||
|
||||
Text text_bus_width_value{
|
||||
{240 - (bus_width_characters * 8), 5 * 16, (bus_width_characters * 8), 16},
|
||||
"",
|
||||
@@ -90,11 +93,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t card_type_characters = 13;
|
||||
|
||||
Text text_card_type_title{
|
||||
{0, 6 * 16, (9 * 8), 16},
|
||||
"Card type",
|
||||
};
|
||||
|
||||
Text text_card_type_value{
|
||||
{240 - (card_type_characters * 8), 6 * 16, (card_type_characters * 8), 16},
|
||||
"",
|
||||
@@ -102,11 +100,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t block_size_characters = 5;
|
||||
|
||||
Text text_block_size_title{
|
||||
{0, 8 * 16, (10 * 8), 16},
|
||||
"Block size",
|
||||
};
|
||||
|
||||
Text text_block_size_value{
|
||||
{240 - (block_size_characters * 8), 8 * 16, (block_size_characters * 8), 16},
|
||||
"",
|
||||
@@ -114,11 +107,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t block_count_characters = 9;
|
||||
|
||||
Text text_block_count_title{
|
||||
{0, 9 * 16, (11 * 8), 16},
|
||||
"Block count",
|
||||
};
|
||||
|
||||
Text text_block_count_value{
|
||||
{240 - (block_count_characters * 8), 9 * 16, (block_count_characters * 8), 16},
|
||||
"",
|
||||
@@ -126,11 +114,6 @@ class SDCardDebugView : public View {
|
||||
|
||||
static constexpr size_t capacity_characters = 10;
|
||||
|
||||
Text text_capacity_title{
|
||||
{0, 10 * 16, (8 * 8), 16},
|
||||
"Capacity",
|
||||
};
|
||||
|
||||
Text text_capacity_value{
|
||||
{240 - (capacity_characters * 8), 10 * 16, (capacity_characters * 8), 16},
|
||||
"",
|
||||
|
||||
@@ -138,7 +138,7 @@ bool strEndsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||
|
||||
static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "Usage: flash /FIRMWARE/portapack-h1_h2-mayhem.bin\r\n");
|
||||
chprintf(chp, "Usage: flash /FIRMWARE/portapack-mayhem-firmware.bin\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -907,6 +907,191 @@ static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t reverse(uint32_t x) {
|
||||
x = ((x >> 1) & 0x55555555u) | ((x & 0x55555555u) << 1);
|
||||
x = ((x >> 2) & 0x33333333u) | ((x & 0x33333333u) << 2);
|
||||
x = ((x >> 4) & 0x0f0f0f0fu) | ((x & 0x0f0f0f0fu) << 4);
|
||||
x = ((x >> 8) & 0x00ff00ffu) | ((x & 0x00ff00ffu) << 8);
|
||||
x = ((x >> 16) & 0xffffu) | ((x & 0xffffu) << 16);
|
||||
return x;
|
||||
}
|
||||
|
||||
static void cmd_cpld_write(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage =
|
||||
"usage: cpld_write <device> <target> <file>\r\n"
|
||||
" device can be: hackrf, portapack\r\n"
|
||||
" target can be: sram (hackrf only), eeprom\r\n"
|
||||
" currently only \"cpld_write portapack eeprom <file>\" is supported\r\n";
|
||||
|
||||
if (argc != 3) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(argv[0], "hackrf", 5) == 0) {
|
||||
chprintf(chp, usage);
|
||||
} else if (strncmp(argv[0], "portapack", 5) == 0) {
|
||||
if (strncmp(argv[1], "eeprom", 5) == 0) {
|
||||
jtag::GPIOTarget target{
|
||||
portapack::gpio_cpld_tck,
|
||||
portapack::gpio_cpld_tms,
|
||||
portapack::gpio_cpld_tdi,
|
||||
portapack::gpio_cpld_tdo};
|
||||
jtag::JTAG jtag{target};
|
||||
portapack::cpld::CPLD cpld{jtag};
|
||||
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
uint32_t idcode = cpld.get_idcode();
|
||||
|
||||
chprintf(chp, "CPLD IDCODE: 0x%08X\r\n", idcode);
|
||||
|
||||
if (idcode == 0x00025610) {
|
||||
chprintf(chp, "CPLD Model: AGM AG256SL100\r\n");
|
||||
|
||||
if (cpld.AGM_enter_maintenance_mode() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
File* file = new File();
|
||||
auto path = path_from_string8(argv[2]);
|
||||
auto error = file->open(path);
|
||||
|
||||
if (error.is_valid()) {
|
||||
chprintf(chp, "Error opening file: %s %d %s\r\n", argv[2], error.value().code(), error.value().what().c_str());
|
||||
delete file;
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = std::make_unique<std::array<uint32_t, 1801>>();
|
||||
uint32_t magic = 0;
|
||||
bool magic_found = false;
|
||||
uint32_t expected_address = 0;
|
||||
|
||||
auto readData = std::vector<uint8_t>();
|
||||
|
||||
chprintf(chp, "Reading file...\r\n");
|
||||
file->seek(0);
|
||||
|
||||
while (!file->eof().value()) {
|
||||
uint32_t remainingData = readData.size();
|
||||
uint32_t bytesToRead = 512 - remainingData;
|
||||
|
||||
readData.resize(512);
|
||||
|
||||
auto result = file->read(readData.data() + remainingData, bytesToRead);
|
||||
|
||||
if (result.is_error()) {
|
||||
chprintf(chp, "Error reading file: %d %s\r\n", result.error().code(), result.error().what().c_str());
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
file->close();
|
||||
delete file;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.value() != 512)
|
||||
readData.resize(remainingData + result.value());
|
||||
|
||||
do {
|
||||
auto it = std::find(readData.begin(), readData.end(), '\n');
|
||||
if (it != readData.end()) {
|
||||
std::string line(readData.begin(), it);
|
||||
readData.erase(readData.begin(), it + 1);
|
||||
|
||||
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
|
||||
line.erase(std::remove(line.begin(), line.end(), '\n'), line.end());
|
||||
|
||||
auto prefix = line.find("sdr 64 -tdi ", 0);
|
||||
auto suffix = line.find("0040", line.size() - 4);
|
||||
|
||||
if (prefix == 0 && suffix == line.size() - 4) {
|
||||
std::string dataString = line.substr(line.size() - 16, 16);
|
||||
|
||||
uint32_t address = reverse(std::stoul(dataString.substr(8, 8), nullptr, 16) - 64) / 4;
|
||||
uint32_t value = std::stoul(dataString.substr(0, 8), nullptr, 16);
|
||||
|
||||
if (expected_address == 299 && address == 0) {
|
||||
magic = value;
|
||||
magic_found = true;
|
||||
chprintf(chp, "Magic found: %08X\r\n", magic);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expected_address != address) {
|
||||
chprintf(chp, "Error: expected address %d, got %d\r\n", expected_address, address);
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
file->close();
|
||||
delete file;
|
||||
return;
|
||||
}
|
||||
|
||||
(*data)[expected_address] = value;
|
||||
expected_address++;
|
||||
|
||||
if (expected_address == 1801) {
|
||||
if (!magic_found) {
|
||||
chprintf(chp, "Error: magic not found\r\n");
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
file->close();
|
||||
delete file;
|
||||
return;
|
||||
}
|
||||
|
||||
chprintf(chp, "Writing data to CPLD...\r\n");
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
cpld.AGM_write(*data, magic);
|
||||
|
||||
cpld.AGM_enter_read_mode();
|
||||
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
for (size_t i = 0; i < 2048; i++) {
|
||||
uint32_t data = cpld.AGM_read(i);
|
||||
crc.process_byte((data >> 0) & 0xff);
|
||||
crc.process_byte((data >> 8) & 0xff);
|
||||
crc.process_byte((data >> 16) & 0xff);
|
||||
crc.process_byte((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
|
||||
chprintf(chp, "New CPLD firmware checksum: 0x%08X\r\n", crc.checksum());
|
||||
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
|
||||
WWDT_TC = 100000 & 0xFFFFFF;
|
||||
WWDT_FEED_SEQUENCE;
|
||||
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
|
||||
} else {
|
||||
chprintf(chp, "CPLD Model: unknown\r\n");
|
||||
}
|
||||
} else {
|
||||
chprintf(chp, usage);
|
||||
}
|
||||
} else {
|
||||
chprintf(chp, usage);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_gotgps(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: gotgps <lat> <lon> [altitude] [speed] [satinuse]\r\n";
|
||||
if (argc < 2 || argc > 5) {
|
||||
@@ -1203,6 +1388,7 @@ static const ShellCommand commands[] = {
|
||||
{"rtcset", cmd_rtcset},
|
||||
{"cpld_info", cpld_info},
|
||||
{"cpld_read", cmd_cpld_read},
|
||||
{"cpld_write", cmd_cpld_write},
|
||||
{"accessibility_readall", cmd_accessibility_readall},
|
||||
{"accessibility_readcurr", cmd_accessibility_readcurr},
|
||||
{"applist", cmd_applist},
|
||||
|
||||
@@ -271,6 +271,7 @@ macro(DeclareTargets chunk_tag name)
|
||||
project("baseband_${name}")
|
||||
|
||||
include(${RULESPATH}/rules.cmake)
|
||||
set_source_files_properties(${MODE_CPPSRC} PROPERTIES COMPILE_FLAGS "${MODE_FLAGS}")
|
||||
add_executable(${PROJECT_NAME}.elf $<TARGET_OBJECTS:baseband_shared> ${MODE_CPPSRC} ${HALSRC} ${PLATFORMSRC})
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
||||
add_definitions(${DEFS})
|
||||
@@ -313,6 +314,7 @@ macro(DeclareTargets chunk_tag name)
|
||||
endmacro()
|
||||
|
||||
set(add_to_firmware TRUE)
|
||||
set(MODE_FLAGS "-O3")
|
||||
|
||||
|
||||
### ADS-B RX
|
||||
@@ -498,6 +500,7 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PWTH weather)
|
||||
|
||||
set(MODE_FLAGS "-Os")
|
||||
|
||||
### Flash Utility
|
||||
|
||||
@@ -559,7 +562,7 @@ DeclareTargets(PUSB sd_over_usb)
|
||||
|
||||
### Place external app and disabled images below so they don't get added to the firmware
|
||||
set(add_to_firmware FALSE)
|
||||
|
||||
set(MODE_FLAGS "-O3")
|
||||
|
||||
### ACARS RX
|
||||
|
||||
|
||||
@@ -332,5 +332,40 @@ uint32_t CPLD::AGM_read(uint32_t address) {
|
||||
return jtag.shift_dr(32, encoded_address, 0x0);
|
||||
}
|
||||
|
||||
void CPLD::AGM_write(const std::array<uint32_t, 1801>& block, uint32_t magic_value) {
|
||||
shift_ir(instruction_t::AGM_SET_REGISTER);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.shift_dr(8, 0xf0);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
shift_ir(instruction_t::AGM_ERASE);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.runtest_ms(500);
|
||||
|
||||
shift_ir(instruction_t::AGM_SET_REGISTER);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.shift_dr(8, 0xf0);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
shift_ir(instruction_t::AGM_PROGRAM);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
auto data = block.data();
|
||||
for (size_t i = 0; i < 0x12B; i++) {
|
||||
auto address = AGM_encode_address(i * 4, 0x40);
|
||||
jtag.shift_dr(32, address, data[i]);
|
||||
jtag.runtest_ms(2);
|
||||
}
|
||||
|
||||
jtag.shift_dr(32, 0x00000040, magic_value);
|
||||
jtag.runtest_ms(2);
|
||||
|
||||
for (size_t i = 0x12B; i < block.size(); i++) {
|
||||
auto address = AGM_encode_address(i * 4, 0x40);
|
||||
jtag.shift_dr(32, address, data[i]);
|
||||
jtag.runtest_ms(2);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace max5 */
|
||||
} /* namespace cpld */
|
||||
|
||||
@@ -99,6 +99,7 @@ class CPLD {
|
||||
void AGM_enter_read_mode();
|
||||
uint32_t AGM_encode_address(uint32_t address, uint32_t trailer);
|
||||
uint32_t AGM_read(uint32_t address);
|
||||
void AGM_write(const std::array<uint32_t, 1801>& block, uint32_t magic_value);
|
||||
|
||||
private:
|
||||
using idcode_t = uint32_t;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "spi_image.hpp"
|
||||
#include "standalone_app.hpp"
|
||||
|
||||
#define CURRENT_HEADER_VERSION 0x00000002
|
||||
#define CURRENT_HEADER_VERSION 0x00000003
|
||||
#define MIN_HEADER_VERSION_FOR_CHECKSUM 0x00000002
|
||||
|
||||
typedef void (*externalAppEntry_t)(ui::NavigationView& nav);
|
||||
@@ -42,6 +42,7 @@ struct application_information_t {
|
||||
uint8_t bitmap_data[32];
|
||||
uint32_t icon_color;
|
||||
app_location_t menu_location;
|
||||
int32_t desired_menu_position;
|
||||
|
||||
portapack::spi_flash::image_tag_t m4_app_tag;
|
||||
uint32_t m4_app_offset;
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
*/
|
||||
|
||||
#define _USE_LFN 2
|
||||
#define _USE_LFN 3
|
||||
#define _MAX_LFN 255
|
||||
/* The _USE_LFN switches the support of long file name (LFN).
|
||||
/
|
||||
@@ -189,7 +189,7 @@
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the file system object (FATFS) is used for the file data transfer. */
|
||||
|
||||
#define _FS_EXFAT 0
|
||||
#define _FS_EXFAT 1
|
||||
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
|
||||
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
@@ -202,16 +202,14 @@ bool I2cDev_MAX17055::init(uint8_t addr_) {
|
||||
model = I2CDEVMDL_MAX17055;
|
||||
query_interval = BATTERY_WIDGET_REFRESH_INTERVAL;
|
||||
if (detect()) {
|
||||
bool return_status = true;
|
||||
if (needsInitialization()) {
|
||||
// First-time or POR initialization
|
||||
full_reset_and_init();
|
||||
} else {
|
||||
// Subsequent boot
|
||||
partialInit();
|
||||
return_status = full_reset_and_init();
|
||||
}
|
||||
partialInit();
|
||||
// statusClear(); I am not sure if this should be here or not
|
||||
return true;
|
||||
partialInit(); // If you always want hibernation disabled
|
||||
// statusClear(); // I am not sure if this should be here or not (Clear all bits in the Status register (0x00))
|
||||
return return_status;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -220,19 +218,15 @@ bool I2cDev_MAX17055::full_reset_and_init() {
|
||||
if (!soft_reset()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initialize_custom_parameters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!load_custom_parameters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!clear_por()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -298,8 +292,7 @@ void I2cDev_MAX17055::partialInit() {
|
||||
bool I2cDev_MAX17055::reset_learned() {
|
||||
// this if for reset all the learned parameters by ic
|
||||
// the full inis should do this
|
||||
full_reset_and_init();
|
||||
return true;
|
||||
return full_reset_and_init();
|
||||
}
|
||||
|
||||
bool I2cDev_MAX17055::detect() {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#define __JTAG_H__
|
||||
|
||||
#include "jtag_target.hpp"
|
||||
#include "ch.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -51,7 +52,16 @@ class JTAG {
|
||||
}
|
||||
|
||||
void runtest_tck(const size_t count) {
|
||||
target.delay(count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
target.clock(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void runtest_ms(const size_t count) {
|
||||
auto starttime = chTimeNow();
|
||||
|
||||
while ((chTimeNow() - starttime) < (count + 1))
|
||||
target.clock(0, 0);
|
||||
}
|
||||
|
||||
uint32_t shift_ir(const size_t count, const uint32_t value) {
|
||||
|
||||
@@ -101,7 +101,11 @@ class MessageQueue {
|
||||
}
|
||||
|
||||
bool push(const void* const buf, const size_t len) {
|
||||
chMtxLock(&mutex_write);
|
||||
bool lock_success = chMtxTryLock(&mutex_write);
|
||||
if (!lock_success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = fifo.in_r(buf, len);
|
||||
chMtxUnlock();
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 886 B |
Binary file not shown.
|
After Width: | Height: | Size: 609 B |
@@ -91,8 +91,8 @@ cmake_objcopy = sys.argv[3]
|
||||
|
||||
memory_location_header_position = 0
|
||||
externalAppEntry_header_position = 4
|
||||
m4_app_tag_header_position = 72
|
||||
m4_app_offset_header_position = 76
|
||||
m4_app_tag_header_position = 76
|
||||
m4_app_offset_header_position = 80
|
||||
|
||||
for external_image_prefix in sys.argv[4:]:
|
||||
|
||||
@@ -123,7 +123,10 @@ for external_image_prefix in sys.argv[4:]:
|
||||
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
|
||||
continue
|
||||
|
||||
print(chunk_data)
|
||||
chunk_tag = chunk_data.decode("utf-8")
|
||||
print(chunk_tag)
|
||||
print("{}/../baseband/{}.bin".format(binary_dir, chunk_tag))
|
||||
m4_image = read_image("{}/../baseband/{}.bin".format(binary_dir, chunk_tag))
|
||||
app_image_len = len(external_application_image)
|
||||
external_application_image += m4_image
|
||||
|
||||
Regular → Executable
+7
-6
@@ -26,6 +26,7 @@ import requests
|
||||
import sys
|
||||
|
||||
cppheader = """#include "ui_about_simple.hpp"
|
||||
#include <string_view>
|
||||
|
||||
#define ROLL_SPEED_FRAME_PER_LINE 60
|
||||
// cuz frame rate of pp screen is probably 60, scroll per sec
|
||||
@@ -33,7 +34,7 @@ cppheader = """#include "ui_about_simple.hpp"
|
||||
namespace ui {
|
||||
|
||||
// Information: a line starting with a '#' will be yellow coloured
|
||||
const std::"""
|
||||
constexpr std::"""
|
||||
|
||||
cppfooter = """
|
||||
AboutView::AboutView(NavigationView& nav) {
|
||||
@@ -52,18 +53,18 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (const std::string& authors_line : authors_list) {
|
||||
for (auto& authors_line : authors_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
menu_view.add_item(
|
||||
{authors_line.substr(1, authors_line.size() - 1),
|
||||
{(std::string)authors_line.substr(1, authors_line.size() - 1),
|
||||
ui::Theme::getInstance()->fg_yellow->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
} else {
|
||||
menu_view.add_item(
|
||||
{authors_line,
|
||||
{(std::string)authors_line,
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
@@ -140,7 +141,7 @@ def get_contributors(url):
|
||||
|
||||
def generate_content(projects):
|
||||
project_contrib = []
|
||||
project_contrib.append("string authors_list[] = {\n")
|
||||
project_contrib.append("string_view authors_list[] = {\n")
|
||||
project_contrib.append(" \"# * List of contributors * \",\n")
|
||||
for project in projects:
|
||||
project_contrib.append(" \" \",\n")
|
||||
@@ -169,7 +170,7 @@ def pp_create_ui_about_simple_cpp(cpp_file, cppheader, cppcontent, cppfooter):
|
||||
|
||||
def pp_change_ui_about_simple_cpp(cpp_file, cppcontent):
|
||||
content = []
|
||||
content_pattern = re.compile(r"string authors_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
content_pattern = re.compile(r"string_view authors_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
|
||||
# Read original file
|
||||
with open(cpp_file, 'r') as file:
|
||||
|
||||
@@ -12,8 +12,8 @@ pause
|
||||
echo.
|
||||
|
||||
REM Check if the firmware file exists
|
||||
if not exist portapack-h1_h2-mayhem.bin (
|
||||
echo The firmware file "portapack-h1_h2-mayhem.bin" does not exist.
|
||||
if not exist portapack-mayhem-firmware.bin (
|
||||
echo The firmware file "portapack-mayhem-firmware.bin" does not exist.
|
||||
echo Please ensure that you have downloaded the latest release from:
|
||||
echo https://github.com/portapack-mayhem/mayhem-firmware/releases/
|
||||
echo.
|
||||
@@ -21,7 +21,7 @@ if not exist portapack-h1_h2-mayhem.bin (
|
||||
exit /b
|
||||
)
|
||||
|
||||
"utils/hackrf_update.exe" portapack-h1_h2-mayhem.bin
|
||||
"utils/hackrf_update.exe" portapack-mayhem-firmware.bin
|
||||
echo.
|
||||
echo If your device never boot after flashing, please refer to https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
|
||||
echo.
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user