mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-24 06:49:24 -05:00
commit
18e89d28a8
9
.github/workflows/create_nightly_release.yml
vendored
9
.github/workflows/create_nightly_release.yml
vendored
@ -54,19 +54,22 @@ jobs:
|
|||||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
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
|
- name: Create Small SD Card ZIP - No World Map
|
||||||
run: |
|
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
|
- name: Download world map
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||||
- name: Unzip world map
|
- name: Unzip world map
|
||||||
run: |
|
run: |
|
||||||
unzip world_map.zip -d sdcard/ADSB
|
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
|
- name: Create Firmware ZIP
|
||||||
run: |
|
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
|
- name: Create SD Card ZIP
|
||||||
run: |
|
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
|
- name: Create changelog
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
9
.github/workflows/create_stable_release.yml
vendored
9
.github/workflows/create_stable_release.yml
vendored
@ -38,19 +38,22 @@ jobs:
|
|||||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
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
|
- name: Create Small SD Card ZIP - No World Map
|
||||||
run: |
|
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
|
- name: Download world map
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||||
- name: Unzip world map
|
- name: Unzip world map
|
||||||
run: |
|
run: |
|
||||||
unzip world_map.zip -d sdcard/ADSB
|
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
|
- name: Create Firmware ZIP
|
||||||
run: |
|
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
|
- name: Create SD Card ZIP
|
||||||
run: |
|
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
|
- name: Create changelog
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
2
.github/workflows/past_version.txt
vendored
2
.github/workflows/past_version.txt
vendored
@ -1 +1 @@
|
|||||||
v2.0.1
|
v2.0.2
|
||||||
|
2
.github/workflows/version.txt
vendored
2
.github/workflows/version.txt
vendored
@ -1 +1 @@
|
|||||||
v2.0.2
|
v2.1.0
|
||||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
|||||||
[submodule "hackrf"]
|
[submodule "hackrf"]
|
||||||
path = hackrf
|
path = hackrf
|
||||||
url = https://github.com/mossmann/hackrf.git
|
url = https://github.com/portapack-mayhem/hackrf.git
|
||||||
|
@ -23,6 +23,12 @@ cmake_policy(SET CMP0005 NEW)
|
|||||||
|
|
||||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||||
|
|
||||||
|
set(CMAKE_COLOR_MAKEFILE ON)
|
||||||
|
|
||||||
|
add_compile_options(-fdiagnostics-color=always)
|
||||||
|
|
||||||
|
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
|
||||||
|
|
||||||
project(portapack-h1)
|
project(portapack-h1)
|
||||||
|
|
||||||
set(EXPECTED_GCC_VERSION "9.2.1")
|
set(EXPECTED_GCC_VERSION "9.2.1")
|
||||||
@ -59,10 +65,11 @@ message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
|||||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||||
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
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_DFU_FILENAME hackrf_usb.dfu)
|
||||||
set(HACKRF_FIRMWARE_BIN_FILENAME hackrf_usb_ram.bin)
|
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_CPLD_XSVF_FILENAME default.xsvf)
|
||||||
|
|
||||||
set(HACKRF_PATH ${CMAKE_CURRENT_LIST_DIR}/hackrf)
|
set(HACKRF_PATH ${CMAKE_CURRENT_LIST_DIR}/hackrf)
|
||||||
@ -72,13 +79,24 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
|
|||||||
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
||||||
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
||||||
|
|
||||||
# this seems causing some issues (ref. in discord discussions), so temporarily disabled, until figure out the pros and cons and bugs of this tool.
|
# this seems causing some issues (ref. in discord discussions), but it sometimes do helps, so you have your choice to use. it's default disabled, unless you use such command:
|
||||||
#find_program(CCACHE "ccache")
|
# cmake -DUSE_CCACHE=ON ..
|
||||||
#if(CCACHE)
|
# `make` or `make -j` or `make -j10` etc
|
||||||
# set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
# the default behavior `cmake ..` isn't changed (aka don't use ccache)
|
||||||
# set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
message(STATUS "-------v ccache info--------")
|
||||||
# set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
if(USE_CCACHE)
|
||||||
#endif(CCACHE)
|
find_program(CCACHE_FOUND ccache)
|
||||||
|
if(CCACHE_FOUND)
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_FOUND})
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_FOUND})
|
||||||
|
message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=OFF .. ` (cmake with Ninja) to turn off ccache")
|
||||||
|
else()
|
||||||
|
message(WARNING "ccache is enabled but not found on the system.")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=ON ..` (cmake with Ninja) to turn on ccache")
|
||||||
|
endif()
|
||||||
|
message(STATUS "-------^ ccache info--------")
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(firmware)
|
add_subdirectory(firmware)
|
||||||
|
21
README.md
21
README.md
@ -1,25 +1,23 @@
|
|||||||
|
<!---
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
|
> __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
|
# PortaPack Mayhem
|
||||||
|
|
||||||
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
|
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
|
||||||
|
|
||||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
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)
|
[<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)
|
||||||
|
|
||||||
*[PortaPack H2+HackRF+battery](https://grabify.link/7T28JP) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
|
||||||
|
|
||||||
# What is this?
|
# What is this?
|
||||||
|
|
||||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||||
|
|
||||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="700">](https://grabify.link/X4D5TF)
|
[<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="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="230">](https://grabify.link/9UZGEW) [<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="230">](https://grabify.link/5MU2VH) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="230">](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
|
# Frequently Asked Questions
|
||||||
|
|
||||||
@ -27,11 +25,12 @@ This repository expands upon the previous work by many people and aims to consta
|
|||||||
|
|
||||||
## What to buy?
|
## What to buy?
|
||||||
|
|
||||||
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous [PortaPack H4M Mayhem](https://grabify.link/VPMPSL), featuring numerous improvements and accessories. Sold by our friends at OpenSourceSDRLab. Join their giveaways on discord (check the badge on top).
|
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||||
|
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/TUOLYI) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||||
|
|
||||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/7T28JP), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||||
|
|
||||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/HTDXG5), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||||
|
|
||||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
|
|||||||
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
||||||
|
|
||||||
## What if I really want something specific?
|
## What if I really want something specific?
|
||||||
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?assignees=&labels=enhancement&projects=&template=02_feature_request.yml). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||||
|
|
||||||
## What if I need help?
|
## What if I need help?
|
||||||
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
|
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:xenial
|
FROM ubuntu:noble
|
||||||
|
|
||||||
# Set location to download ARM toolkit from.
|
# 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.
|
# 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
|
# Fetch dependencies from APT
|
||||||
RUN apt-get update \
|
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 \
|
&& apt-get -qy autoremove \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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 LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:xenial
|
FROM ubuntu:noble
|
||||||
|
|
||||||
# Set location to download ARM toolkit from.
|
# 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.
|
# 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
|
# Fetch dependencies from APT
|
||||||
RUN apt-get update \
|
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 \
|
&& apt-get -qy autoremove \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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 LANG C.UTF-8
|
||||||
ENV LC_ALL 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(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
|
||||||
set(LZ4 lz4)
|
set(LZ4 lz4)
|
||||||
|
|
||||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
set(FIRMWARE_NAME portapack-mayhem-firmware)
|
||||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||||
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ add_custom_command(
|
|||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
firmware
|
firmware
|
||||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} ${HACKRF_FIRMWARE_FILENAME}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(${GCC_VERSION_MISMATCH})
|
if(${GCC_VERSION_MISMATCH})
|
||||||
|
@ -117,6 +117,7 @@ set(CSRC
|
|||||||
usb_serial_descriptor.c
|
usb_serial_descriptor.c
|
||||||
usb_serial_endpoints.c
|
usb_serial_endpoints.c
|
||||||
usb_serial_device_to_host.c
|
usb_serial_device_to_host.c
|
||||||
|
i2c_device_to_host.c
|
||||||
${HACKRF_PATH}/firmware/common/usb.c
|
${HACKRF_PATH}/firmware/common/usb.c
|
||||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||||
@ -125,12 +126,14 @@ set(CSRC
|
|||||||
${CHIBIOS}/os/various/chprintf.c
|
${CHIBIOS}/os/various/chprintf.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#look for all i2cdev_ files
|
||||||
|
file(GLOB I2CDEV_SOURCES ${COMMON}/i2cdev_*.cpp)
|
||||||
|
|
||||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
||||||
# setting.
|
# setting.
|
||||||
set(CPPSRC
|
set(CPPSRC
|
||||||
main.cpp
|
main.cpp
|
||||||
shell.cpp
|
shell.cpp
|
||||||
${COMMON}/acars_packet.cpp
|
|
||||||
${COMMON}/adsb.cpp
|
${COMMON}/adsb.cpp
|
||||||
${COMMON}/adsb_frame.cpp
|
${COMMON}/adsb_frame.cpp
|
||||||
${COMMON}/ais_baseband.cpp
|
${COMMON}/ais_baseband.cpp
|
||||||
@ -176,8 +179,8 @@ set(CPPSRC
|
|||||||
${COMMON}/ui_language.cpp
|
${COMMON}/ui_language.cpp
|
||||||
${COMMON}/utility.cpp
|
${COMMON}/utility.cpp
|
||||||
${COMMON}/wm8731.cpp
|
${COMMON}/wm8731.cpp
|
||||||
${COMMON}/ads1110.cpp
|
${COMMON}/i2cdevmanager.cpp
|
||||||
${COMMON}/max17055.cpp
|
${I2CDEV_SOURCES}
|
||||||
${COMMON}/battery.cpp
|
${COMMON}/battery.cpp
|
||||||
${COMMON}/performance_counter.cpp
|
${COMMON}/performance_counter.cpp
|
||||||
${COMMON}/bmpfile.cpp
|
${COMMON}/bmpfile.cpp
|
||||||
@ -189,7 +192,6 @@ set(CPPSRC
|
|||||||
core_control.cpp
|
core_control.cpp
|
||||||
database.cpp
|
database.cpp
|
||||||
de_bruijn.cpp
|
de_bruijn.cpp
|
||||||
#emu_cc1101.cpp
|
|
||||||
rfm69.cpp
|
rfm69.cpp
|
||||||
event_m0.cpp
|
event_m0.cpp
|
||||||
file_reader.cpp
|
file_reader.cpp
|
||||||
@ -242,9 +244,9 @@ set(CPPSRC
|
|||||||
hw/si5351.cpp
|
hw/si5351.cpp
|
||||||
hw/spi_pp.cpp
|
hw/spi_pp.cpp
|
||||||
hw/touch_adc.cpp
|
hw/touch_adc.cpp
|
||||||
|
ook_file.cpp
|
||||||
ui_baseband_stats_view.cpp
|
ui_baseband_stats_view.cpp
|
||||||
ui_navigation.cpp
|
ui_navigation.cpp
|
||||||
ui_playdead.cpp
|
|
||||||
ui_record_view.cpp
|
ui_record_view.cpp
|
||||||
ui_sd_card_status_view.cpp
|
ui_sd_card_status_view.cpp
|
||||||
ui/ui_alphanum.cpp
|
ui/ui_alphanum.cpp
|
||||||
@ -266,56 +268,41 @@ set(CPPSRC
|
|||||||
ui/ui_tone_key.cpp
|
ui/ui_tone_key.cpp
|
||||||
ui/ui_transmitter.cpp
|
ui/ui_transmitter.cpp
|
||||||
ui/ui_bmpview.cpp
|
ui/ui_bmpview.cpp
|
||||||
apps/acars_app.cpp
|
|
||||||
apps/ais_app.cpp
|
apps/ais_app.cpp
|
||||||
apps/analog_audio_app.cpp
|
apps/analog_audio_app.cpp
|
||||||
# apps/analog_tv_app.cpp
|
|
||||||
apps/ble_comm_app.cpp
|
apps/ble_comm_app.cpp
|
||||||
apps/ble_rx_app.cpp
|
apps/ble_rx_app.cpp
|
||||||
apps/ble_tx_app.cpp
|
apps/ble_tx_app.cpp
|
||||||
apps/capture_app.cpp
|
apps/capture_app.cpp
|
||||||
apps/ert_app.cpp
|
apps/ert_app.cpp
|
||||||
# apps/gps_sim_app.cpp
|
|
||||||
# apps/lge_app.cpp
|
|
||||||
apps/pocsag_app.cpp
|
apps/pocsag_app.cpp
|
||||||
# apps/replay_app.cpp
|
|
||||||
apps/soundboard_app.cpp
|
apps/soundboard_app.cpp
|
||||||
# apps/tpms_app.cpp
|
|
||||||
apps/ui_about_simple.cpp
|
apps/ui_about_simple.cpp
|
||||||
apps/ui_adsb_rx.cpp
|
apps/ui_adsb_rx.cpp
|
||||||
# apps/ui_adsb_tx.cpp #moved to ext
|
|
||||||
apps/ui_aprs_rx.cpp
|
apps/ui_aprs_rx.cpp
|
||||||
apps/ui_aprs_tx.cpp
|
apps/ui_aprs_tx.cpp
|
||||||
apps/ui_battinfo.cpp
|
apps/ui_battinfo.cpp
|
||||||
apps/ui_bht_tx.cpp
|
apps/ui_bht_tx.cpp
|
||||||
apps/ui_bmp_file_viewer.cpp
|
apps/ui_bmp_file_viewer.cpp
|
||||||
apps/ui_btle_rx.cpp
|
apps/ui_btle_rx.cpp
|
||||||
# apps/ui_coasterp.cpp
|
|
||||||
apps/ui_debug.cpp
|
apps/ui_debug.cpp
|
||||||
apps/ui_debug_battery.cpp
|
apps/ui_debug_max17055.cpp
|
||||||
apps/ui_dfu_menu.cpp
|
apps/ui_dfu_menu.cpp
|
||||||
apps/ui_encoders.cpp
|
apps/ui_encoders.cpp
|
||||||
|
apps/ui_external_module_view.cpp
|
||||||
apps/ui_fileman.cpp
|
apps/ui_fileman.cpp
|
||||||
apps/ui_flash_utility.cpp
|
apps/ui_flash_utility.cpp
|
||||||
apps/ui_freqman.cpp
|
apps/ui_freqman.cpp
|
||||||
apps/ui_fsk_rx.cpp
|
|
||||||
apps/ui_iq_trim.cpp
|
apps/ui_iq_trim.cpp
|
||||||
# apps/ui_jammer.cpp
|
|
||||||
# apps/ui_keyfob.cpp
|
|
||||||
# apps/ui_lcr.cpp
|
|
||||||
apps/ui_level.cpp
|
apps/ui_level.cpp
|
||||||
apps/ui_looking_glass_app.cpp
|
apps/ui_looking_glass_app.cpp
|
||||||
apps/ui_mictx.cpp
|
apps/ui_mictx.cpp
|
||||||
apps/ui_modemsetup.cpp
|
apps/ui_modemsetup.cpp
|
||||||
# apps/ui_morse.cpp
|
|
||||||
# apps/ui_nrf_rx.cpp
|
|
||||||
# apps/ui_nuoptix.cpp
|
|
||||||
apps/ui_playlist.cpp
|
apps/ui_playlist.cpp
|
||||||
apps/ui_pocsag_tx.cpp
|
apps/ui_pocsag_tx.cpp
|
||||||
apps/ui_rds.cpp
|
apps/ui_rds.cpp
|
||||||
apps/ui_recon_settings.cpp
|
apps/ui_recon_settings.cpp
|
||||||
apps/ui_recon.cpp
|
apps/ui_recon.cpp
|
||||||
apps/ui_remote.cpp
|
|
||||||
apps/ui_scanner.cpp
|
apps/ui_scanner.cpp
|
||||||
apps/ui_sd_over_usb.cpp
|
apps/ui_sd_over_usb.cpp
|
||||||
apps/ui_sd_wipe.cpp
|
apps/ui_sd_wipe.cpp
|
||||||
@ -323,16 +310,11 @@ set(CPPSRC
|
|||||||
apps/ui_settings.cpp
|
apps/ui_settings.cpp
|
||||||
apps/ui_siggen.cpp
|
apps/ui_siggen.cpp
|
||||||
apps/ui_sonde.cpp
|
apps/ui_sonde.cpp
|
||||||
# apps/ui_spectrum_painter_image.cpp
|
|
||||||
# apps/ui_spectrum_painter_text.cpp
|
|
||||||
# apps/ui_spectrum_painter.cpp
|
|
||||||
apps/ui_ss_viewer.cpp
|
apps/ui_ss_viewer.cpp
|
||||||
# apps/ui_sstvtx.cpp #moved to ext
|
|
||||||
apps/ui_standalone_view.cpp
|
apps/ui_standalone_view.cpp
|
||||||
apps/ui_subghzd.cpp
|
apps/ui_subghzd.cpp
|
||||||
# apps/ui_test.cpp
|
# apps/ui_test.cpp
|
||||||
apps/ui_text_editor.cpp
|
apps/ui_text_editor.cpp
|
||||||
apps/ui_tone_search.cpp
|
|
||||||
apps/ui_touch_calibration.cpp
|
apps/ui_touch_calibration.cpp
|
||||||
apps/ui_touchtunes.cpp
|
apps/ui_touchtunes.cpp
|
||||||
apps/ui_view_wav.cpp
|
apps/ui_view_wav.cpp
|
||||||
@ -343,14 +325,8 @@ set(CPPSRC
|
|||||||
protocols/bht.cpp
|
protocols/bht.cpp
|
||||||
protocols/dcs.cpp
|
protocols/dcs.cpp
|
||||||
protocols/encoders.cpp
|
protocols/encoders.cpp
|
||||||
# protocols/lcr.cpp
|
|
||||||
protocols/modems.cpp
|
protocols/modems.cpp
|
||||||
protocols/rds.cpp
|
protocols/rds.cpp
|
||||||
# ui_handwrite.cpp
|
|
||||||
# ui_loadmodule.cpp
|
|
||||||
# ui_numbers.cpp
|
|
||||||
# ui_replay_view.cpp
|
|
||||||
# ui_script.cpp
|
|
||||||
ui_sd_card_debug.cpp
|
ui_sd_card_debug.cpp
|
||||||
config_mode.cpp
|
config_mode.cpp
|
||||||
${CPLD_20150901_DATA_CPP}
|
${CPLD_20150901_DATA_CPP}
|
||||||
|
@ -87,9 +87,12 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
|||||||
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
||||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||||
|
|
||||||
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz
|
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
|
||||||
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
|
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
|
||||||
option_format.set_selected_index(1);
|
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
|
||||||
|
}
|
||||||
|
if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
|
||||||
|
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
|
||||||
}
|
}
|
||||||
previous_bandwidth = bandwidth;
|
previous_bandwidth = bandwidth;
|
||||||
|
|
||||||
|
@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
* Copyleft (ↄ) 2022 NotPike
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "replay_app.hpp"
|
|
||||||
#include "string_format.hpp"
|
|
||||||
|
|
||||||
#include "ui_fileman.hpp"
|
|
||||||
#include "io_file.hpp"
|
|
||||||
#include "io_convert.hpp"
|
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "metadata_file.hpp"
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
|
||||||
#include "utility.hpp"
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
void ReplayAppView::set_ready() {
|
|
||||||
ready_signal = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
|
||||||
file_path = new_file_path;
|
|
||||||
File::Size file_size{};
|
|
||||||
|
|
||||||
{ // Get the size of the data file.
|
|
||||||
File data_file;
|
|
||||||
auto error = data_file.open(file_path);
|
|
||||||
if (error) {
|
|
||||||
file_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_size = data_file.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get original record frequency if available.
|
|
||||||
auto metadata_path = get_metadata_path(file_path);
|
|
||||||
auto metadata = read_metadata_file(metadata_path);
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
field_frequency.set_value(metadata->center_frequency);
|
|
||||||
sample_rate = metadata->sample_rate;
|
|
||||||
} else {
|
|
||||||
// TODO: This is interesting because it implies that the
|
|
||||||
// The capture will just be replayed at the freq set on the
|
|
||||||
// FrequencyField. Is that an intentional behavior?
|
|
||||||
sample_rate = 500000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI Fixup.
|
|
||||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
|
|
||||||
progressbar.set_max(file_size);
|
|
||||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
|
||||||
|
|
||||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
|
||||||
auto duration = ms_duration(file_size, sample_rate, sample_size);
|
|
||||||
text_duration.set(to_string_time_ms(duration));
|
|
||||||
|
|
||||||
button_play.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::on_tx_progress(const uint32_t progress) {
|
|
||||||
progressbar.set_value(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::focus() {
|
|
||||||
button_open.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::file_error() {
|
|
||||||
nav_.display_modal("Error", "File read error.");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReplayAppView::is_active() const {
|
|
||||||
return (bool)replay_thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::toggle() {
|
|
||||||
if (is_active()) {
|
|
||||||
stop(false);
|
|
||||||
} else {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::start() {
|
|
||||||
stop(false);
|
|
||||||
|
|
||||||
std::unique_ptr<stream::Reader> reader;
|
|
||||||
|
|
||||||
auto p = std::make_unique<FileConvertReader>();
|
|
||||||
auto open_error = p->open(file_path);
|
|
||||||
if (open_error.is_valid()) {
|
|
||||||
file_error();
|
|
||||||
return; // Fixes TX bug if there's a file error
|
|
||||||
} else {
|
|
||||||
reader = std::move(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader) {
|
|
||||||
button_play.set_bitmap(&bitmap_stop);
|
|
||||||
baseband::set_sample_rate(sample_rate, OversampleRate::x8);
|
|
||||||
|
|
||||||
replay_thread = std::make_unique<ReplayThread>(
|
|
||||||
std::move(reader),
|
|
||||||
read_size, buffer_count,
|
|
||||||
&ready_signal,
|
|
||||||
[](uint32_t return_code) {
|
|
||||||
ReplayThreadDoneMessage message{return_code};
|
|
||||||
EventDispatcher::send_message(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
transmitter_model.set_sampling_rate(sample_rate * toUType(OversampleRate::x8));
|
|
||||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
|
||||||
transmitter_model.enable();
|
|
||||||
|
|
||||||
if (portapack::persistent_memory::stealth_mode()) {
|
|
||||||
DisplaySleepMessage message;
|
|
||||||
EventDispatcher::send_message(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::stop(const bool do_loop) {
|
|
||||||
if (is_active())
|
|
||||||
replay_thread.reset();
|
|
||||||
|
|
||||||
if (do_loop && check_loop.value()) {
|
|
||||||
start();
|
|
||||||
} else {
|
|
||||||
transmitter_model.disable();
|
|
||||||
button_play.set_bitmap(&bitmap_play);
|
|
||||||
}
|
|
||||||
|
|
||||||
ready_signal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::handle_replay_thread_done(const uint32_t return_code) {
|
|
||||||
if (return_code == ReplayThread::END_OF_FILE) {
|
|
||||||
stop(true);
|
|
||||||
} else if (return_code == ReplayThread::READ_ERROR) {
|
|
||||||
stop(false);
|
|
||||||
file_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
progressbar.set_value(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplayAppView::ReplayAppView(
|
|
||||||
NavigationView& nav)
|
|
||||||
: nav_(nav) {
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
|
||||||
|
|
||||||
add_children({
|
|
||||||
&button_open,
|
|
||||||
&text_filename,
|
|
||||||
&text_sample_rate,
|
|
||||||
&text_duration,
|
|
||||||
&progressbar,
|
|
||||||
&field_frequency,
|
|
||||||
&tx_view, // now it handles previous rfgain, rfamp.
|
|
||||||
&check_loop,
|
|
||||||
&button_play,
|
|
||||||
&waterfall,
|
|
||||||
});
|
|
||||||
|
|
||||||
button_play.on_select = [this](ImageButton&) {
|
|
||||||
this->toggle();
|
|
||||||
};
|
|
||||||
|
|
||||||
button_open.on_select = [this, &nav](Button&) {
|
|
||||||
auto open_view = nav.push<FileLoadView>(".C*");
|
|
||||||
open_view->on_changed = [this](fs::path new_file_path) {
|
|
||||||
on_file_changed(new_file_path);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplayAppView::~ReplayAppView() {
|
|
||||||
transmitter_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::on_hide() {
|
|
||||||
stop(false);
|
|
||||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
|
||||||
// it's being shown or hidden.
|
|
||||||
waterfall.on_hide();
|
|
||||||
View::on_hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
|
|
||||||
View::set_parent_rect(new_parent_rect);
|
|
||||||
|
|
||||||
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
|
||||||
waterfall.set_parent_rect(waterfall_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __REPLAY_APP_HPP__
|
|
||||||
#define __REPLAY_APP_HPP__
|
|
||||||
|
|
||||||
#include "app_settings.hpp"
|
|
||||||
#include "radio_state.hpp"
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "ui_receiver.hpp"
|
|
||||||
#include "ui_freq_field.hpp"
|
|
||||||
#include "replay_thread.hpp"
|
|
||||||
#include "ui_spectrum.hpp"
|
|
||||||
#include "ui_transmitter.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class ReplayAppView : public View {
|
|
||||||
public:
|
|
||||||
ReplayAppView(NavigationView& nav);
|
|
||||||
~ReplayAppView();
|
|
||||||
|
|
||||||
void on_hide() override;
|
|
||||||
void set_parent_rect(const Rect new_parent_rect) override;
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Replay"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
NavigationView& nav_;
|
|
||||||
TxRadioState radio_state_{};
|
|
||||||
app_settings::SettingsManager settings_{
|
|
||||||
"tx_replay", app_settings::Mode::TX};
|
|
||||||
|
|
||||||
static constexpr ui::Dim header_height = 3 * 16;
|
|
||||||
|
|
||||||
uint32_t sample_rate = 0;
|
|
||||||
int32_t tx_gain{47};
|
|
||||||
bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
|
|
||||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
|
||||||
const size_t read_size{16384};
|
|
||||||
const size_t buffer_count{3};
|
|
||||||
|
|
||||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
|
||||||
void on_tx_progress(const uint32_t progress);
|
|
||||||
|
|
||||||
void toggle();
|
|
||||||
void start();
|
|
||||||
void stop(const bool do_loop);
|
|
||||||
bool is_active() const;
|
|
||||||
void set_ready();
|
|
||||||
void handle_replay_thread_done(const uint32_t return_code);
|
|
||||||
void file_error();
|
|
||||||
|
|
||||||
std::filesystem::path file_path{};
|
|
||||||
std::unique_ptr<ReplayThread> replay_thread{};
|
|
||||||
bool ready_signal{false};
|
|
||||||
|
|
||||||
Button button_open{
|
|
||||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
|
||||||
"Open file"};
|
|
||||||
|
|
||||||
Text text_filename{
|
|
||||||
{11 * 8, 0 * 16, 12 * 8, 16},
|
|
||||||
"-"};
|
|
||||||
Text text_sample_rate{
|
|
||||||
{24 * 8, 0 * 16, 6 * 8, 16},
|
|
||||||
"-"};
|
|
||||||
|
|
||||||
Text text_duration{
|
|
||||||
{11 * 8, 1 * 16, 6 * 8, 16},
|
|
||||||
"-"};
|
|
||||||
ProgressBar progressbar{
|
|
||||||
{18 * 8, 1 * 16, 12 * 8, 16}};
|
|
||||||
|
|
||||||
TxFrequencyField field_frequency{
|
|
||||||
{0 * 8, 2 * 16},
|
|
||||||
nav_};
|
|
||||||
|
|
||||||
TransmitterView2 tx_view{
|
|
||||||
{11 * 8, 2 * 16},
|
|
||||||
/*short_ui*/ true};
|
|
||||||
|
|
||||||
Checkbox check_loop{
|
|
||||||
{21 * 8, 2 * 16},
|
|
||||||
4,
|
|
||||||
"Loop",
|
|
||||||
true};
|
|
||||||
ImageButton button_play{
|
|
||||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
|
||||||
&bitmap_play,
|
|
||||||
Theme::getInstance()->fg_green->foreground,
|
|
||||||
Theme::getInstance()->fg_green->background};
|
|
||||||
|
|
||||||
spectrum::WaterfallView waterfall{};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
|
||||||
Message::ID::ReplayThreadDone,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
|
||||||
this->handle_replay_thread_done(message.return_code);
|
|
||||||
}};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_fifo_signal{
|
|
||||||
Message::ID::RequestSignal,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
|
||||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
|
||||||
this->set_ready();
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_tx_progress{
|
|
||||||
Message::ID::TXProgress,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
|
||||||
this->on_tx_progress(message.progress);
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__REPLAY_APP_HPP__*/
|
|
@ -35,6 +35,8 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
#define FILE_PER_PAGE 50
|
||||||
|
|
||||||
bool SoundBoardView::is_active() const {
|
bool SoundBoardView::is_active() const {
|
||||||
return (bool)replay_thread;
|
return (bool)replay_thread;
|
||||||
}
|
}
|
||||||
@ -178,9 +180,9 @@ void SoundBoardView::refresh_list() {
|
|||||||
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
|
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
|
||||||
// sounds[c].ms_duration = reader->ms_duration();
|
// sounds[c].ms_duration = reader->ms_duration();
|
||||||
// sounds[c].path = u"WAV/" + entry.path().native();
|
// sounds[c].path = u"WAV/" + entry.path().native();
|
||||||
if (count >= (page - 1) * 100 && count < page * 100) {
|
if (count >= (page - 1) * FILE_PER_PAGE && count < page * FILE_PER_PAGE) {
|
||||||
file_list.push_back(entry.path());
|
file_list.push_back(entry.path());
|
||||||
if (file_list.size() == 100) {
|
if (file_list.size() == FILE_PER_PAGE) {
|
||||||
page++;
|
page++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -225,7 +227,7 @@ void SoundBoardView::refresh_list() {
|
|||||||
menu_view.set_highlighted(0); // Refresh
|
menu_view.set_highlighted(0); // Refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_list.size() < 100) {
|
if (file_list.size() < FILE_PER_PAGE) {
|
||||||
page = 1;
|
page = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "cpld_update.hpp"
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "event_m0.hpp"
|
|
||||||
|
|
||||||
#include "ui_about.hpp"
|
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
|
||||||
#include "lpc43xx_cpp.hpp"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
using namespace lpc43xx;
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
// This is pretty much WaterfallView but in the opposite direction
|
|
||||||
CreditsWidget::CreditsWidget(
|
|
||||||
Rect parent_rect)
|
|
||||||
: Widget{parent_rect} {
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreditsWidget::paint(Painter&) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreditsWidget::on_show() {
|
|
||||||
clear();
|
|
||||||
|
|
||||||
const auto screen_r = screen_rect();
|
|
||||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreditsWidget::on_hide() {
|
|
||||||
display.scroll_disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreditsWidget::new_row(
|
|
||||||
const std::array<Color, 240>& pixel_row) {
|
|
||||||
// Glitch be here (see comment in main.cpp)
|
|
||||||
const auto draw_y = display.scroll(-1);
|
|
||||||
|
|
||||||
display.draw_pixels(
|
|
||||||
{{0, draw_y - 1}, {240, 1}},
|
|
||||||
pixel_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreditsWidget::clear() {
|
|
||||||
display.fill_rectangle(
|
|
||||||
screen_rect(),
|
|
||||||
Theme::getInstance()->bg_darkest->background);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::update() {
|
|
||||||
size_t i = 0;
|
|
||||||
std::array<Color, 240> pixel_row;
|
|
||||||
|
|
||||||
slow_down++;
|
|
||||||
if (slow_down % 3 < 2) return;
|
|
||||||
|
|
||||||
if (!timer) {
|
|
||||||
if (loop) {
|
|
||||||
credits_index = 0;
|
|
||||||
loop = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
text = credits[credits_index].text;
|
|
||||||
timer = credits[credits_index].delay;
|
|
||||||
start_pos = credits[credits_index].start_pos;
|
|
||||||
|
|
||||||
if (timer < 0) {
|
|
||||||
timer = 240;
|
|
||||||
loop = true;
|
|
||||||
} else
|
|
||||||
timer += 16;
|
|
||||||
|
|
||||||
render_line = 0;
|
|
||||||
credits_index++;
|
|
||||||
} else
|
|
||||||
timer--;
|
|
||||||
|
|
||||||
if (render_line < 16) {
|
|
||||||
for (const auto c : text) {
|
|
||||||
const auto glyph = style().font.glyph(c);
|
|
||||||
|
|
||||||
const size_t start = (glyph.size().width() / 8) * render_line;
|
|
||||||
for (Dim c = 0; c < glyph.size().width(); c++) {
|
|
||||||
const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
|
|
||||||
pixel_row[start_pos + i + c] = pixel ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto advance = glyph.advance();
|
|
||||||
i += advance.x();
|
|
||||||
}
|
|
||||||
render_line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
credits_display.new_row(pixel_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutView::AboutView(
|
|
||||||
NavigationView& nav) {
|
|
||||||
add_children({&credits_display,
|
|
||||||
&button_ok});
|
|
||||||
|
|
||||||
button_ok.on_select = [&nav](Button&) {
|
|
||||||
nav.pop();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::focus() {
|
|
||||||
button_ok.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __UI_ABOUT_H__
|
|
||||||
#define __UI_ABOUT_H__
|
|
||||||
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class CreditsWidget : public Widget {
|
|
||||||
public:
|
|
||||||
CreditsWidget(Rect parent_rect);
|
|
||||||
|
|
||||||
void on_show() override;
|
|
||||||
void on_hide() override;
|
|
||||||
|
|
||||||
void paint(Painter&) override;
|
|
||||||
|
|
||||||
void new_row(const std::array<Color, 240>& pixel_row);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
class AboutView : public View {
|
|
||||||
public:
|
|
||||||
AboutView(NavigationView& nav);
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "About"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
void update();
|
|
||||||
|
|
||||||
uint8_t credits_index{0};
|
|
||||||
uint8_t render_line{0};
|
|
||||||
Coord start_pos{0};
|
|
||||||
uint8_t slow_down{0};
|
|
||||||
int32_t timer{0};
|
|
||||||
bool loop{false};
|
|
||||||
|
|
||||||
std::string text{};
|
|
||||||
|
|
||||||
typedef struct credits_t {
|
|
||||||
size_t start_pos;
|
|
||||||
std::string text;
|
|
||||||
int32_t delay;
|
|
||||||
} credits_t;
|
|
||||||
|
|
||||||
// TODO: Make this dinamically centered and parse \n as the delay value so it is easy to maintain
|
|
||||||
const credits_t credits[26] = {
|
|
||||||
// 012345678901234567890123456789
|
|
||||||
{60, "PortaPack Mayhem", 0},
|
|
||||||
{60, "PortaPack|HAVOC", 0},
|
|
||||||
{11 * 8, "Gurus J. Boone", 0},
|
|
||||||
{18 * 8, "M. Ossmann", 16},
|
|
||||||
{11 * 8, "HAVOC Furrtek", 16},
|
|
||||||
{7 * 8, "POCSAG rx T. Sailer", 0},
|
|
||||||
{18 * 8, "E. Oenal", 16},
|
|
||||||
{0 * 8, "Radiosonde infos F4GMU", 0},
|
|
||||||
{18 * 8, "RS1729", 16},
|
|
||||||
{4 * 8, "RDS waveform C. Jacquet", 16},
|
|
||||||
{7 * 8, "Xy. infos cLx", 16},
|
|
||||||
{2 * 8, "OOK scan trick Samy Kamkar", 16},
|
|
||||||
{7 * 8, "World map NASA", 16},
|
|
||||||
{0 * 8, "TouchTunes infos Notpike", 16},
|
|
||||||
{4 * 8, "Subaru infos Tom", 0},
|
|
||||||
{18 * 8, "Wimmenhove", 16},
|
|
||||||
{1 * 8, "GPS,TV,BTLE,NRF Shao", 24},
|
|
||||||
{6 * 8, "Thanks & donators", 16},
|
|
||||||
{1 * 8, "Rainer Matla Keld Norman", 0},
|
|
||||||
{1 * 8, " Giorgio C. DC1RDB", 0},
|
|
||||||
{1 * 8, " Sigmounte Waax", 0},
|
|
||||||
{1 * 8, " Windyoona Channels", 0},
|
|
||||||
{1 * 8, " F4GEV Pyr3x", 0},
|
|
||||||
{1 * 8, " HB3YOE", 24},
|
|
||||||
{11 * 8, "MMXVIII", -1}};
|
|
||||||
|
|
||||||
CreditsWidget credits_display{
|
|
||||||
{0, 16, 240, 240}};
|
|
||||||
|
|
||||||
Button button_ok{
|
|
||||||
{72, 272, 96, 24},
|
|
||||||
"OK"};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_update{
|
|
||||||
Message::ID::DisplayFrameSync,
|
|
||||||
[this](const Message* const) {
|
|
||||||
this->update();
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__UI_ABOUT_H__*/
|
|
@ -1,413 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ch.h>
|
|
||||||
|
|
||||||
#include "demofont.hpp"
|
|
||||||
#include "ymdata.hpp"
|
|
||||||
|
|
||||||
#include "cpld_update.hpp"
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "audio.hpp"
|
|
||||||
#include "event_m0.hpp"
|
|
||||||
|
|
||||||
#include "ui_about.hpp"
|
|
||||||
#include "touch.hpp"
|
|
||||||
#include "sine_table.hpp"
|
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
|
||||||
#include "lpc43xx_cpp.hpp"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
using namespace lpc43xx;
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
void AboutView::on_show() {
|
|
||||||
transmitter_model.set_target_frequency(1337000000); // TODO: Change
|
|
||||||
transmitter_model.set_baseband_configuration({
|
|
||||||
.mode = 0,
|
|
||||||
.sampling_rate = 1536000,
|
|
||||||
.decimation_factor = 1,
|
|
||||||
});
|
|
||||||
transmitter_model.set_rf_amp(true);
|
|
||||||
transmitter_model.set_lna(40);
|
|
||||||
transmitter_model.set_vga(40);
|
|
||||||
transmitter_model.enable();
|
|
||||||
|
|
||||||
baseband::set_audiotx_data(32, 50, false, 0);
|
|
||||||
|
|
||||||
// audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::render_video() {
|
|
||||||
uint8_t p, r, luma, chroma, cy;
|
|
||||||
ui::Color cc;
|
|
||||||
char ch;
|
|
||||||
float s;
|
|
||||||
|
|
||||||
// Send framebuffer to LCD. Gotta go fast !
|
|
||||||
display.render_box({30, 112}, {180, 72}, framebuffer);
|
|
||||||
|
|
||||||
// Clear framebuffer to black
|
|
||||||
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
|
||||||
|
|
||||||
// Drum hit palette animation
|
|
||||||
if (drum > 1) drum--;
|
|
||||||
|
|
||||||
// Render copper bars from Y buffer
|
|
||||||
for (p = 0; p < 72; p++) {
|
|
||||||
luma = copperbuffer[p] & 0x0F; // 0 is transparent
|
|
||||||
if (luma) {
|
|
||||||
chroma = copperbuffer[p] >> 4;
|
|
||||||
cc = ui::Color(std::min((coppercolor[chroma][0] / luma) * drum, 255), std::min((coppercolor[chroma][1] / luma) * drum, 255), std::min((coppercolor[chroma][2] / luma) * drum, 255));
|
|
||||||
for (r = 0; r < 180; r++)
|
|
||||||
framebuffer[(p * 180) + r] = cc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll in/out state machine
|
|
||||||
if (anim_state == 0) {
|
|
||||||
// Scroll in
|
|
||||||
if (ofy < 8) {
|
|
||||||
ofy++;
|
|
||||||
anim_state = 0;
|
|
||||||
} else {
|
|
||||||
anim_state = 1;
|
|
||||||
}
|
|
||||||
if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
|
|
||||||
ofx += 8;
|
|
||||||
anim_state = 0;
|
|
||||||
}
|
|
||||||
} else if (anim_state == 1) {
|
|
||||||
// Just wait
|
|
||||||
if (credits_timer == (30 * 3)) {
|
|
||||||
credits_timer = 0;
|
|
||||||
anim_state = 2;
|
|
||||||
} else {
|
|
||||||
credits_timer++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Scroll out
|
|
||||||
if (credits[credits_index].change == true) {
|
|
||||||
if (ofy > -24) {
|
|
||||||
ofy--;
|
|
||||||
anim_state = 2;
|
|
||||||
} else {
|
|
||||||
anim_state = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
anim_state = 0;
|
|
||||||
}
|
|
||||||
if (ofx < 180) {
|
|
||||||
ofx += 8;
|
|
||||||
anim_state = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to next text
|
|
||||||
if (anim_state == 0) {
|
|
||||||
if (credits_index == 9)
|
|
||||||
credits_index = 0;
|
|
||||||
else
|
|
||||||
credits_index++;
|
|
||||||
ofx = -(strlen(credits[credits_index].name) * 16) - 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sine text ("role")
|
|
||||||
p = 0;
|
|
||||||
while ((ch = credits[credits_index].role[p])) {
|
|
||||||
draw_demoglyph({(ui::Coord)(8 + (p * 16)), (ui::Coord)(ofy + (sine_table_f32[((p * 16) + (phase >> 5)) & 0xFF] * 8))}, ch, paletteA);
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll text (name)
|
|
||||||
p = 0;
|
|
||||||
while ((ch = credits[credits_index].name[p])) {
|
|
||||||
draw_demoglyph({(ui::Coord)(ofx + (p * 16)), 56}, ch, paletteB);
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear bars Y buffer
|
|
||||||
memset(copperbuffer, 0, 72);
|
|
||||||
|
|
||||||
// Render bars to Y buffer
|
|
||||||
for (p = 0; p < 5; p++) {
|
|
||||||
cy = copperbars[p];
|
|
||||||
for (r = 0; r < 16; r++)
|
|
||||||
copperbuffer[cy + r] = copperluma[r] + (p << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate bars positions
|
|
||||||
for (p = 0; p < 5; p++) {
|
|
||||||
s = sine_table_f32[((p * 32) + (phase / 24)) & 0xFF];
|
|
||||||
s += sine_table_f32[((p * 16) + (phase / 35)) & 0xFF];
|
|
||||||
copperbars[p] = 28 + (uint8_t)(s * 14);
|
|
||||||
}
|
|
||||||
|
|
||||||
phase += 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
|
|
||||||
uint8_t x, y, c, cl, cr;
|
|
||||||
uint16_t che;
|
|
||||||
int16_t lbx, il;
|
|
||||||
|
|
||||||
// Map ASCII to font bitmap
|
|
||||||
if ((ch >= 32) && (ch < 96))
|
|
||||||
che = char_map[ch - 32];
|
|
||||||
else
|
|
||||||
che = 0xFF;
|
|
||||||
|
|
||||||
if (che < 0xFF) {
|
|
||||||
che = (che * 128) + 48; // Start in bitmap
|
|
||||||
|
|
||||||
il = (180 * p.y) + p.x; // Start il framebuffer
|
|
||||||
|
|
||||||
for (y = 0; y < 16; y++) {
|
|
||||||
if (p.y + y >= 72) break; // Over bottom of framebuffer, abort
|
|
||||||
if (p.y + y >= 0) {
|
|
||||||
for (x = 0; x < 8; x++) {
|
|
||||||
c = demofont_bin[x + (y * 8) + che]; // Split byte in 2 4BPP pixels
|
|
||||||
cl = c >> 4;
|
|
||||||
cr = c & 0x0F;
|
|
||||||
lbx = p.x + (x * 2);
|
|
||||||
if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
|
|
||||||
lbx++;
|
|
||||||
il++;
|
|
||||||
if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
|
|
||||||
il++;
|
|
||||||
}
|
|
||||||
il += 180 - 16;
|
|
||||||
} else {
|
|
||||||
il += 180;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::render_audio() {
|
|
||||||
uint8_t i, ymdata;
|
|
||||||
uint16_t ym_render_cnt;
|
|
||||||
|
|
||||||
// This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet
|
|
||||||
|
|
||||||
// Render 1024 music samples
|
|
||||||
for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
|
|
||||||
// Update registers at 48000/960 = 50Hz
|
|
||||||
if (ym_sample_cnt == 0) {
|
|
||||||
// "Decompress" on the fly and update YM registers
|
|
||||||
for (i = 0; i < 14; i++) {
|
|
||||||
if (!ym_regs[i].cnt) {
|
|
||||||
// New run
|
|
||||||
ymdata = ymdata_bin[ym_regs[i].ptr++];
|
|
||||||
ym_regs[i].cnt = ymdata & 0x7F;
|
|
||||||
if (ymdata & 0x80) {
|
|
||||||
ym_regs[i].same = true;
|
|
||||||
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
|
||||||
} else {
|
|
||||||
ym_regs[i].same = false;
|
|
||||||
}
|
|
||||||
// Detect drum on channel B
|
|
||||||
if (i == 3)
|
|
||||||
if (ym_regs[3].value > 2) drum = 4;
|
|
||||||
}
|
|
||||||
if (ym_regs[i].same == false) {
|
|
||||||
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
|
||||||
if (i == 13) {
|
|
||||||
// Update envelope attributes
|
|
||||||
ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
|
|
||||||
if (!(ym_regs[13].value & 8)) {
|
|
||||||
ym_env_hold = 1;
|
|
||||||
ym_env_alt = ym_env_att;
|
|
||||||
} else {
|
|
||||||
ym_env_hold = ym_regs[13].value & 1;
|
|
||||||
ym_env_alt = ym_regs[13].value & 2;
|
|
||||||
}
|
|
||||||
// Reset envelope counter
|
|
||||||
ym_env_step = 0x1F;
|
|
||||||
ym_env_holding = 0;
|
|
||||||
ym_env_vol = (ym_env_step ^ ym_env_att);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ym_regs[i].cnt--;
|
|
||||||
}
|
|
||||||
ym_frame++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Square wave oscillators
|
|
||||||
// 2457600/16/48000 = 3.2, but 4 sounds better than 3...
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
ym_osc_cnt[i] += 4;
|
|
||||||
if (ym_osc_cnt[i] >= (ym_regs[i * 2].value | ((ym_regs[(i * 2) + 1].value & 0x0f) << 8))) {
|
|
||||||
ym_osc_cnt[i] = 0;
|
|
||||||
ym_osc_out[i] ^= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noise generator
|
|
||||||
ym_noise_cnt += 4;
|
|
||||||
if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
|
|
||||||
ym_noise_cnt = 0;
|
|
||||||
ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
|
|
||||||
ym_rng >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mix tones and noise
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));
|
|
||||||
|
|
||||||
// Envelope generator
|
|
||||||
if (!ym_env_holding) {
|
|
||||||
ym_env_cnt += 8;
|
|
||||||
if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value << 8))) {
|
|
||||||
ym_env_cnt = 0;
|
|
||||||
ym_env_step--;
|
|
||||||
if (ym_env_step < 0) {
|
|
||||||
if (ym_env_hold) {
|
|
||||||
if (ym_env_alt)
|
|
||||||
ym_env_att ^= 0x1F;
|
|
||||||
ym_env_holding = 1;
|
|
||||||
ym_env_step = 0;
|
|
||||||
} else {
|
|
||||||
if (ym_env_alt && (ym_env_step & 0x20))
|
|
||||||
ym_env_att ^= 0x1F;
|
|
||||||
ym_env_step &= 0x1F;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ym_env_vol = (ym_env_step ^ ym_env_att);
|
|
||||||
|
|
||||||
ym_out = 0;
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
if (ym_regs[i + 8].value & 0x10) {
|
|
||||||
// Envelope mode
|
|
||||||
ym_out += (ym_ch[i] ? ym_env_vol : 0);
|
|
||||||
} else {
|
|
||||||
// Fixed mode
|
|
||||||
ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;
|
|
||||||
|
|
||||||
if (ym_sample_cnt < 960) {
|
|
||||||
ym_sample_cnt++;
|
|
||||||
} else {
|
|
||||||
ym_sample_cnt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop
|
|
||||||
if (ym_frame == ym_frames) ym_init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::update() {
|
|
||||||
if (framebuffer) {
|
|
||||||
// Update 1 out of 2 frames, 60Hz is very laggy
|
|
||||||
if (refresh_cnt & 1) render_video();
|
|
||||||
refresh_cnt++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slowly increase volume to avoid jumpscare
|
|
||||||
if (headphone_vol < (70 << 2)) {
|
|
||||||
audio::headphone::set_volume(volume_t::decibel((headphone_vol / 4) - 99) + audio::headphone::volume_range().max);
|
|
||||||
headphone_vol++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::ym_init() {
|
|
||||||
uint8_t reg;
|
|
||||||
|
|
||||||
for (reg = 0; reg < 14; reg++) {
|
|
||||||
ym_regs[reg].cnt = 0;
|
|
||||||
// Pick up start pointers for each YM registers RLE blocks
|
|
||||||
ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg * 2) + 3]) << 8) + ymdata_bin[(reg * 2) + 2];
|
|
||||||
ym_regs[reg].same = false; // Useless ?
|
|
||||||
ym_regs[reg].value = 0; // Useless ?
|
|
||||||
}
|
|
||||||
|
|
||||||
ym_frame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutView::AboutView(
|
|
||||||
NavigationView& nav) {
|
|
||||||
uint8_t p, c;
|
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
|
||||||
|
|
||||||
add_children({{
|
|
||||||
&text_title,
|
|
||||||
&text_firmware,
|
|
||||||
&text_cpld_hackrf,
|
|
||||||
&text_cpld_hackrf_status,
|
|
||||||
&button_ok,
|
|
||||||
}});
|
|
||||||
|
|
||||||
if (cpld_hackrf_verify_eeprom()) {
|
|
||||||
text_cpld_hackrf_status.set(" OK");
|
|
||||||
} else {
|
|
||||||
text_cpld_hackrf_status.set("BAD");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Politely ask for about 26kB
|
|
||||||
framebuffer = (ui::Color*)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));
|
|
||||||
|
|
||||||
if (framebuffer) {
|
|
||||||
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
|
||||||
|
|
||||||
// Copy original font palette
|
|
||||||
c = 0;
|
|
||||||
for (p = 0; p < 48; p += 3)
|
|
||||||
paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p + 1], demofont_bin[p + 2]);
|
|
||||||
|
|
||||||
// Increase red in another one
|
|
||||||
c = 0;
|
|
||||||
for (p = 0; p < 48; p += 3)
|
|
||||||
paletteB[c++] = ui::Color(std::min(demofont_bin[p] + 64, 255), demofont_bin[p + 1], demofont_bin[p + 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init YM synth
|
|
||||||
ym_frames = ((uint16_t)(ymdata_bin[1]) << 8) + ymdata_bin[0];
|
|
||||||
ym_init();
|
|
||||||
|
|
||||||
button_ok.on_select = [this, &nav](Button&) {
|
|
||||||
if (framebuffer) chHeapFree(framebuffer); // Do NOT forget this
|
|
||||||
nav.pop();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutView::~AboutView() {
|
|
||||||
transmitter_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AboutView::focus() {
|
|
||||||
button_ok.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __UI_ABOUT_H__
|
|
||||||
#define __UI_ABOUT_H__
|
|
||||||
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_menu.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "transmitter_model.hpp"
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class AboutView : public View {
|
|
||||||
public:
|
|
||||||
AboutView(NavigationView& nav);
|
|
||||||
~AboutView();
|
|
||||||
|
|
||||||
void on_show() override;
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void ym_init();
|
|
||||||
void update();
|
|
||||||
void render_video();
|
|
||||||
void render_audio();
|
|
||||||
void draw_demoglyph(ui::Point p, char ch, ui::Color* pal);
|
|
||||||
uint16_t debug_cnt = 0;
|
|
||||||
|
|
||||||
typedef struct ymreg_t {
|
|
||||||
uint8_t value;
|
|
||||||
uint8_t cnt;
|
|
||||||
uint16_t ptr;
|
|
||||||
bool same;
|
|
||||||
} ymreg_t;
|
|
||||||
|
|
||||||
uint16_t headphone_vol = 5 << 2;
|
|
||||||
|
|
||||||
ymreg_t ym_regs[14];
|
|
||||||
uint16_t ym_frames;
|
|
||||||
uint16_t ym_frame;
|
|
||||||
uint8_t drum = 0;
|
|
||||||
uint16_t ym_osc_cnt[3];
|
|
||||||
uint32_t ym_rng = 1;
|
|
||||||
uint16_t ym_noise_cnt;
|
|
||||||
uint8_t ym_env_att, ym_env_hold, ym_env_alt, ym_env_holding, ym_env_vol;
|
|
||||||
int8_t ym_env_step;
|
|
||||||
uint16_t ym_env_cnt;
|
|
||||||
uint8_t ym_osc_out[3];
|
|
||||||
uint8_t ym_ch[3];
|
|
||||||
uint8_t ym_out;
|
|
||||||
uint16_t ym_sample_cnt = 0;
|
|
||||||
|
|
||||||
int8_t ym_buffer[1024];
|
|
||||||
|
|
||||||
uint8_t refresh_cnt;
|
|
||||||
ui::Color paletteA[16];
|
|
||||||
ui::Color paletteB[16];
|
|
||||||
ui::Color* framebuffer;
|
|
||||||
uint32_t phase = 0;
|
|
||||||
uint8_t copperbars[5] = {0};
|
|
||||||
uint8_t copperbuffer[72] = {0};
|
|
||||||
|
|
||||||
uint8_t anim_state = 0;
|
|
||||||
uint8_t credits_index = 0;
|
|
||||||
uint16_t credits_timer = 0;
|
|
||||||
|
|
||||||
int16_t ofx = -180, ofy = -24;
|
|
||||||
|
|
||||||
const uint8_t char_map[64] = {0xFF, 27, 46, 0xFF, 0xFF, 0xFF, 28, 45,
|
|
||||||
58, 59, 0xFF, 43, 40, 57, 26, 42,
|
|
||||||
29, 30, 31, 32, 33, 34, 35, 36,
|
|
||||||
37, 38, 41, 0xFF, 0xFF, 0xFF, 0xFF, 44,
|
|
||||||
0xFF, 0, 1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, 9, 10, 11, 12, 13, 14,
|
|
||||||
15, 16, 17, 18, 19, 20, 21, 22,
|
|
||||||
23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
||||||
|
|
||||||
const uint8_t copperluma[16] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8};
|
|
||||||
const uint8_t coppercolor[5][3] = {{255, 0, 0},
|
|
||||||
{0, 255, 0},
|
|
||||||
{0, 0, 255},
|
|
||||||
{255, 0, 255},
|
|
||||||
{255, 255, 0}};
|
|
||||||
|
|
||||||
typedef struct credits_t {
|
|
||||||
char role[12];
|
|
||||||
char name[12];
|
|
||||||
bool change;
|
|
||||||
} credits_t;
|
|
||||||
|
|
||||||
// 0123456789A 0123456789A
|
|
||||||
const credits_t credits[10] = {{"GURUS", "J. BOONE", false},
|
|
||||||
{"GURUS", "M. OSSMANN", true},
|
|
||||||
{"BUGS", "FURRTEK", true},
|
|
||||||
{"RDS WAVE", "C. JACQUET", true},
|
|
||||||
{"POCSAG RX", "T. SAILER", false},
|
|
||||||
{"POCSAG RX", "E. OENAL", true},
|
|
||||||
{"XYLOS DATA", "CLX", true},
|
|
||||||
{"GREETS TO", "SIGMOUNTE", false},
|
|
||||||
{"GREETS TO", "WINDYOONA", true},
|
|
||||||
{"THIS MUSIC", "BIG ALEC", true}};
|
|
||||||
|
|
||||||
Text text_title{
|
|
||||||
{100, 32, 40, 16},
|
|
||||||
"About",
|
|
||||||
};
|
|
||||||
|
|
||||||
Text text_firmware{
|
|
||||||
{0, 236, 240, 16},
|
|
||||||
"Version " VERSION_STRING,
|
|
||||||
};
|
|
||||||
|
|
||||||
Text text_cpld_hackrf{
|
|
||||||
{0, 252, 11 * 8, 16},
|
|
||||||
"HackRF CPLD",
|
|
||||||
};
|
|
||||||
|
|
||||||
Text text_cpld_hackrf_status{
|
|
||||||
{240 - 3 * 8, 252, 3 * 8, 16},
|
|
||||||
"???"};
|
|
||||||
|
|
||||||
Button button_ok{
|
|
||||||
{72, 272, 96, 24},
|
|
||||||
"OK"};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_update{
|
|
||||||
Message::ID::DisplayFrameSync,
|
|
||||||
[this](const Message* const) {
|
|
||||||
this->update();
|
|
||||||
}};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_fifo_signal{
|
|
||||||
Message::ID::FIFOSignal,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = static_cast<const FIFOSignalMessage*>(p);
|
|
||||||
if (message->signaltype == 1) {
|
|
||||||
this->render_audio();
|
|
||||||
baseband::set_fifo_data(ym_buffer);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__UI_ABOUT_H__*/
|
|
@ -1,47 +1,60 @@
|
|||||||
#include "ui_about_simple.hpp"
|
#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 {
|
namespace ui {
|
||||||
|
|
||||||
// TODO: Generate this automatically from github
|
|
||||||
// Information: a line starting with a '#' will be yellow coloured
|
// Information: a line starting with a '#' will be yellow coloured
|
||||||
const std::string authors_list[] = {
|
constexpr std::string_view authors_list[] = {
|
||||||
"# * List of contributors * ",
|
"# * List of contributors * ",
|
||||||
" ",
|
" ",
|
||||||
"#Mayhem:",
|
"#Mayhem-Firmware:",
|
||||||
"eried,euquiq,gregoryfenton",
|
"jboone,eried,furrtek,",
|
||||||
"johnelder,jwetzell,nnemanjan00",
|
"NotherNgineer,gullradriel,",
|
||||||
"N0vaPixel,klockee,gullradriel",
|
"jLynx,kallanreed,Brumi-2021,",
|
||||||
"jamesshao8,ITAxReal,rascafr",
|
"htotoo,bernd-herzog,zxkmm,",
|
||||||
"mcules,dqs105,strijar",
|
"ArjanOnwezen,euquiq,u-foka,",
|
||||||
"zhang00963,RedFox-Fr,aldude999",
|
"iNetro,heurist1,dqs105,",
|
||||||
"East2West,fossum,ArjanOnwezen",
|
"teixeluis,jwetzell,",
|
||||||
"vXxOinvizioNxX,teixeluis",
|
"jimilinuxguy,gregoryfenton,",
|
||||||
"Brumi-2021,texasyojimbo",
|
"notpike,strijar,BehleZebub,",
|
||||||
"heurist1,intoxsick,ckuethe",
|
"arneluehrs,rascafr,joyel24,",
|
||||||
"notpike,jLynx,zigad",
|
"ImDroided,zigad,johnelder,",
|
||||||
"MichalLeonBorsuk,jimilinuxguy",
|
"klockee,nnesetto,LupusE,",
|
||||||
"kallanreed,bernd-herzog",
|
"argilo,dc2dc,formtapez,",
|
||||||
"NotherNgineer,zxkmm,u-foka",
|
"RocketGod-git,mrmookie,",
|
||||||
"Netro,HTotoo",
|
"ITAxReal,F33RNI,F4GEV,",
|
||||||
|
"rusty-labs,mjwaxios,andrej-mk,",
|
||||||
|
"RedFox-Fr,nemanjan00,",
|
||||||
|
"MichalLeonBorsuk,",
|
||||||
|
"MatiasFernandez,Giorgiofox,",
|
||||||
|
"ckuethe",
|
||||||
" ",
|
" ",
|
||||||
"#Havoc:",
|
"#Havoc:",
|
||||||
"furrtek,mrmookie,NotPike",
|
"jboone,furrtek,eried,argilo,",
|
||||||
"mjwaxios,ImDroided,Giorgiofox",
|
"mrmookie,Giorgiofox,ImDroided,",
|
||||||
"F4GEV,z4ziggy,xmycroftx",
|
"mjwaxios,F4GEV,OpCode1300,",
|
||||||
"troussos,silascutler",
|
"ZeroChaos-,RndmNmbr,",
|
||||||
"nickbouwhuis,msoose,leres",
|
"silascutler,troussos,z4ziggy,",
|
||||||
"joakar,dhoetger,clem-42",
|
"clem-42,dhoetger,NickBouwhuis,",
|
||||||
"brianlechthaler,ZeroChaos-...",
|
"xmycroftx,Maescool,KimIV,",
|
||||||
|
"joakar,leres,brianlechthaler,",
|
||||||
|
"N0vaPixel",
|
||||||
" ",
|
" ",
|
||||||
"#PortaPack:",
|
"#PortaPack:",
|
||||||
"jboone,argilo",
|
"jboone,mossmann,martinling,",
|
||||||
|
"argilo,eried,ZeroChaos-,",
|
||||||
|
"RndmNmbr",
|
||||||
" ",
|
" ",
|
||||||
"#HackRF:",
|
"#HackRF:",
|
||||||
"mossmann,dominicgs,bvernoux",
|
"mossmann,jboone,dominicgs,",
|
||||||
"bgamari,schneider42,miek",
|
"martinling,bvernoux,miek,",
|
||||||
"willcode,hessu,Sec42",
|
"bgamari,schneider42,straithe,",
|
||||||
"yhetti,ckuethe,smunaut",
|
"grvvy,willcode,hessu,yhetti,",
|
||||||
"wishi,mrbubble62,scateu..."};
|
"Sec42,ckuethe",
|
||||||
|
" "};
|
||||||
|
|
||||||
AboutView::AboutView(NavigationView& nav) {
|
AboutView::AboutView(NavigationView& nav) {
|
||||||
add_children({&menu_view,
|
add_children({&menu_view,
|
||||||
@ -59,18 +72,18 @@ AboutView::AboutView(NavigationView& nav) {
|
|||||||
button_ok.focus();
|
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 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.size() > 0) {
|
||||||
if (authors_line[0] == '#') {
|
if (authors_line[0] == '#') {
|
||||||
menu_view.add_item(
|
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,
|
ui::Theme::getInstance()->fg_yellow->foreground,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr});
|
nullptr});
|
||||||
} else {
|
} else {
|
||||||
menu_view.add_item(
|
menu_view.add_item(
|
||||||
{authors_line,
|
{(std::string)authors_line,
|
||||||
Theme::getInstance()->bg_darkest->foreground,
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr});
|
nullptr});
|
||||||
@ -79,10 +92,42 @@ AboutView::AboutView(NavigationView& nav) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AboutView::on_frame_sync() {
|
||||||
|
if (interacted) return;
|
||||||
|
|
||||||
|
if (frame_sync_count++ % ROLL_SPEED_FRAME_PER_LINE == 0) {
|
||||||
|
const auto current = menu_view.highlighted_index();
|
||||||
|
const auto count = menu_view.item_count();
|
||||||
|
|
||||||
|
if (current < count - 1) {
|
||||||
|
menu_view.set_highlighted(current + 1);
|
||||||
|
} else {
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||||
|
menu_view.set_highlighted(2); // the first line that has human name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AboutView::focus() {
|
void AboutView::focus() {
|
||||||
|
button_ok.focus();
|
||||||
|
menu_view.set_highlighted(2); // the first line that has human name
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AboutView::on_touch(const TouchEvent) {
|
||||||
|
interacted = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AboutView::on_key(const KeyEvent) {
|
||||||
|
interacted = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AboutView::on_encoder(const EncoderEvent) {
|
||||||
|
interacted = true;
|
||||||
menu_view.focus();
|
menu_view.focus();
|
||||||
// put focus on last text line to make it more obvious that list is scrollable
|
return false;
|
||||||
menu_view.set_highlighted(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -14,6 +14,13 @@ class AboutView : public View {
|
|||||||
std::string title() const override { return "About"; };
|
std::string title() const override { return "About"; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual bool on_key(const KeyEvent event);
|
||||||
|
virtual bool on_encoder(const EncoderEvent event);
|
||||||
|
virtual bool on_touch(const TouchEvent event);
|
||||||
|
|
||||||
|
bool interacted{false};
|
||||||
|
uint16_t frame_sync_count{0};
|
||||||
|
void on_frame_sync();
|
||||||
MenuView menu_view{
|
MenuView menu_view{
|
||||||
{0, 0, 240, 264},
|
{0, 0, 240, 264},
|
||||||
true};
|
true};
|
||||||
@ -22,6 +29,12 @@ class AboutView : public View {
|
|||||||
{240 / 3, 270, 240 / 3, 24},
|
{240 / 3, 270, 240 / 3, 24},
|
||||||
"OK",
|
"OK",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_frame_sync{
|
||||||
|
Message::ID::DisplayFrameSync,
|
||||||
|
[this](const Message* const) {
|
||||||
|
this->on_frame_sync();
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
||||||
|
@ -94,21 +94,39 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
|||||||
record_view.set_sampling_rate(24000);
|
record_view.set_sampling_rate(24000);
|
||||||
|
|
||||||
options_region.on_change = [this](size_t, int32_t i) {
|
options_region.on_change = [this](size_t, int32_t i) {
|
||||||
if (i == 0) {
|
field_frequency.on_change = [this](rf::Frequency f) { (void)f; };
|
||||||
|
if (i == 0) { // MAN Manual setting
|
||||||
|
field_frequency.set_value(aprs_rx_freq);
|
||||||
|
} else if (i == 1) { // NA - North America - is also the default
|
||||||
field_frequency.set_value(144390000);
|
field_frequency.set_value(144390000);
|
||||||
} else if (i == 1) {
|
} else if (i == 2) { // EUR
|
||||||
field_frequency.set_value(144800000);
|
field_frequency.set_value(144800000);
|
||||||
} else if (i == 2) {
|
} else if (i == 3) { // AUS
|
||||||
field_frequency.set_value(145175000);
|
field_frequency.set_value(145175000);
|
||||||
} else if (i == 3) {
|
} else if (i == 4) { // NZ
|
||||||
field_frequency.set_value(144575000);
|
field_frequency.set_value(144575000);
|
||||||
} else if (i == 4) {
|
} else if (i == 5) { // ISS
|
||||||
field_frequency.set_value(145825000);
|
field_frequency.set_value(145825000);
|
||||||
}
|
}
|
||||||
|
options_region_id = i;
|
||||||
|
receiver_model.set_target_frequency(field_frequency.value()); // Retune
|
||||||
|
field_frequency.on_change = [this](rf::Frequency f) {
|
||||||
|
aprs_rx_freq = f;
|
||||||
|
options_region.set_selected_index(0, true);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
field_frequency.set_step(100);
|
field_frequency.set_step(100);
|
||||||
options_region.set_selected_index(0, true);
|
field_frequency.on_edit = [this]() {
|
||||||
|
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
|
||||||
|
freq_view->on_changed = [this](rf::Frequency f) {
|
||||||
|
aprs_rx_freq = f;
|
||||||
|
field_frequency.set_value(f);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: setting the region is also going to sef field_frequency.on_change
|
||||||
|
options_region.set_selected_index(options_region_id, true);
|
||||||
|
|
||||||
logger = std::make_unique<APRSLogger>();
|
logger = std::make_unique<APRSLogger>();
|
||||||
if (logger)
|
if (logger)
|
||||||
@ -124,6 +142,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
|||||||
|
|
||||||
void APRSRxView::on_freqchg(int64_t freq) {
|
void APRSRxView::on_freqchg(int64_t freq) {
|
||||||
field_frequency.set_value(freq);
|
field_frequency.set_value(freq);
|
||||||
|
aprs_rx_freq = freq;
|
||||||
|
options_region.set_selected_index(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APRSRxView::on_packet(const APRSPacketMessage* message) {
|
void APRSRxView::on_packet(const APRSPacketMessage* message) {
|
||||||
|
@ -190,6 +190,8 @@ class APRSRxView : public View {
|
|||||||
private:
|
private:
|
||||||
void on_data(uint32_t value, bool is_data);
|
void on_data(uint32_t value, bool is_data);
|
||||||
bool reset_console = false;
|
bool reset_console = false;
|
||||||
|
uint8_t options_region_id = 1; // default to North America
|
||||||
|
rf::Frequency aprs_rx_freq{144390000}; // default to North America frequency
|
||||||
|
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{
|
RxRadioState radio_state_{
|
||||||
@ -198,7 +200,10 @@ class APRSRxView : public View {
|
|||||||
3072000 /* sampling rate */
|
3072000 /* sampling rate */
|
||||||
};
|
};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_aprs", app_settings::Mode::RX};
|
"rx_aprs",
|
||||||
|
app_settings::Mode::RX,
|
||||||
|
{{"options_region_id"sv, &options_region_id},
|
||||||
|
{"aprs_rx_freq"sv, &aprs_rx_freq}}};
|
||||||
|
|
||||||
uint8_t console_color{0};
|
uint8_t console_color{0};
|
||||||
std::string str_log{""};
|
std::string str_log{""};
|
||||||
@ -220,15 +225,15 @@ class APRSRxView : public View {
|
|||||||
OptionsField options_region{
|
OptionsField options_region{
|
||||||
{0 * 8, 0 * 8},
|
{0 * 8, 0 * 8},
|
||||||
3,
|
3,
|
||||||
{{"NA ", 0},
|
{{"MAN", 0},
|
||||||
{"EUR", 1},
|
{"NA ", 1},
|
||||||
{"AUS", 2},
|
{"EUR", 2},
|
||||||
{"NZ ", 3},
|
{"AUS", 3},
|
||||||
{"ISS", 4}}};
|
{"NZ ", 4},
|
||||||
|
{"ISS", 5}}};
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
FrequencyField field_frequency{
|
||||||
{3 * 8, 0 * 16},
|
{3 * 8, 0 * 16}};
|
||||||
nav_};
|
|
||||||
|
|
||||||
// DEBUG
|
// DEBUG
|
||||||
RecordView record_view{
|
RecordView record_view{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@ -49,10 +50,10 @@ void BattinfoView::update_result() {
|
|||||||
text_voltage.set("UNKNOWN");
|
text_voltage.set("UNKNOWN");
|
||||||
text_current.set("-");
|
text_current.set("-");
|
||||||
text_charge.set("-");
|
text_charge.set("-");
|
||||||
text_cycles.set("-");
|
// text_cycles.set("-");
|
||||||
text_ttef.set("-");
|
text_ttef.set("-");
|
||||||
text_method.set("-");
|
text_method.set("-");
|
||||||
text_warn.set("");
|
// text_warn.set("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool uichg = false;
|
bool uichg = false;
|
||||||
@ -74,7 +75,19 @@ void BattinfoView::update_result() {
|
|||||||
text_current.hidden(false);
|
text_current.hidden(false);
|
||||||
text_charge.hidden(false);
|
text_charge.hidden(false);
|
||||||
text_current.set(to_string_dec_int(current) + " mA");
|
text_current.set(to_string_dec_int(current) + " mA");
|
||||||
text_charge.set(current >= 0 ? "Charging" : "Discharging");
|
if (current >= 25) // when >25mA it is charging in any scenario
|
||||||
|
text_charge.set("Charging");
|
||||||
|
else {
|
||||||
|
if (current > -25) { // between -25 and 25
|
||||||
|
if (voltage > 4060 || percent == 100) { // the voltage is high enough, so it is full, that's why no mA
|
||||||
|
text_charge.set("Full");
|
||||||
|
} else {
|
||||||
|
text_charge.set("Holding"); // not enough voltage, so should charge. maybe the batttery is off, or USB power is not enough
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_charge.set("Discharging"); // less then -25mA, so it is discharging
|
||||||
|
}
|
||||||
|
}
|
||||||
labels_opt.hidden(false);
|
labels_opt.hidden(false);
|
||||||
|
|
||||||
text_ttef.hidden(false);
|
text_ttef.hidden(false);
|
||||||
@ -83,26 +96,26 @@ void BattinfoView::update_result() {
|
|||||||
labels_opt.hidden(true);
|
labels_opt.hidden(true);
|
||||||
text_current.hidden(true);
|
text_current.hidden(true);
|
||||||
text_charge.hidden(true);
|
text_charge.hidden(true);
|
||||||
text_cycles.hidden(true);
|
// text_cycles.hidden(true);
|
||||||
text_ttef.hidden(true);
|
text_ttef.hidden(true);
|
||||||
text_warn.set("");
|
// text_warn.set("");
|
||||||
}
|
}
|
||||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
|
/* if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
|
||||||
text_cycles.hidden(false);
|
// text_cycles.hidden(false);
|
||||||
uint16_t cycles = battery::BatteryManagement::get_cycles();
|
uint16_t cycles = 0; // battery::BatteryManagement::get_cycles();
|
||||||
if (cycles < 2)
|
if (cycles < 2)
|
||||||
text_warn.set("SoC improves after 2 cycles");
|
text_warn.set("SoC improves after each cycles");
|
||||||
else
|
else
|
||||||
text_warn.set("");
|
text_warn.set("");
|
||||||
text_cycles.set(to_string_dec_uint(cycles));
|
// text_cycles.set(to_string_dec_uint(cycles));
|
||||||
} else {
|
} else {
|
||||||
text_cycles.hidden(true);
|
// text_cycles.hidden(true);
|
||||||
text_warn.set("");
|
text_warn.set("");
|
||||||
}
|
} */
|
||||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
|
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
|
||||||
text_ttef.hidden(false);
|
text_ttef.hidden(false);
|
||||||
float ttef = 0;
|
float ttef = 0;
|
||||||
if (current <= 0) {
|
if (current <= 0) { // we keep this yet
|
||||||
ttef = battery::BatteryManagement::get_tte();
|
ttef = battery::BatteryManagement::get_tte();
|
||||||
} else {
|
} else {
|
||||||
ttef = battery::BatteryManagement::get_ttf();
|
ttef = battery::BatteryManagement::get_ttf();
|
||||||
@ -132,7 +145,7 @@ void BattinfoView::update_result() {
|
|||||||
}
|
}
|
||||||
if (uichg) set_dirty();
|
if (uichg) set_dirty();
|
||||||
// to update status bar too, send message in behalf of batt manager
|
// to update status bar too, send message in behalf of batt manager
|
||||||
BatteryStateMessage msg{valid_mask, percent, current >= 0, voltage};
|
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
|
||||||
EventDispatcher::send_message(msg);
|
EventDispatcher::send_message(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +160,8 @@ BattinfoView::BattinfoView(NavigationView& nav)
|
|||||||
&text_method,
|
&text_method,
|
||||||
&button_mode,
|
&button_mode,
|
||||||
&button_exit,
|
&button_exit,
|
||||||
&text_cycles,
|
// &text_cycles,
|
||||||
&text_warn,
|
// &text_warn,
|
||||||
&text_ttef});
|
&text_ttef});
|
||||||
|
|
||||||
button_exit.on_select = [this, &nav](Button&) {
|
button_exit.on_select = [this, &nav](Button&) {
|
||||||
@ -185,4 +198,4 @@ BattinfoView::~BattinfoView() {
|
|||||||
thread = nullptr;
|
thread = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace ui
|
} // namespace ui
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@ -27,6 +28,7 @@
|
|||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
class BattinfoView : public View {
|
class BattinfoView : public View {
|
||||||
@ -61,7 +63,7 @@ class BattinfoView : public View {
|
|||||||
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
|
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
// {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
|
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,13 +85,13 @@ class BattinfoView : public View {
|
|||||||
Text text_ttef{
|
Text text_ttef{
|
||||||
{13 * 8, 6 * 16, 10 * 16, 16},
|
{13 * 8, 6 * 16, 10 * 16, 16},
|
||||||
"-"};
|
"-"};
|
||||||
Text text_cycles{
|
/* Text text_cycles{
|
||||||
{13 * 8, 7 * 16, 10 * 16, 16},
|
{13 * 8, 7 * 16, 10 * 16, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Text text_warn{
|
Text text_warn{
|
||||||
{2 * 8, 8 * 16, 30 * 8, 2 * 16},
|
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
|
||||||
""};
|
""}; */
|
||||||
|
|
||||||
Button button_mode{
|
Button button_mode{
|
||||||
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
|
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
|
||||||
@ -104,4 +106,4 @@ class BattinfoView : public View {
|
|||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|
||||||
#endif /*__UI_BATTINFO__*/
|
#endif /*__UI_BATTINFO__*/
|
@ -36,7 +36,8 @@
|
|||||||
#include "ui_font_fixed_8x16.hpp"
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
#include "ui_painter.hpp"
|
#include "ui_painter.hpp"
|
||||||
#include "ui_external_items_menu_loader.hpp"
|
#include "ui_external_items_menu_loader.hpp"
|
||||||
#include "ui_debug_battery.hpp"
|
#include "ui_debug_max17055.hpp"
|
||||||
|
#include "ui_external_module_view.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
@ -73,91 +74,6 @@ void DebugMemoryView::focus() {
|
|||||||
button_done.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::RegistersWidget(
|
RegistersWidget::RegistersWidget(
|
||||||
@ -226,8 +142,10 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
|
|||||||
return radio::debug::second_if::register_read(register_number);
|
return radio::debug::second_if::register_read(register_number);
|
||||||
case CT_SI5351:
|
case CT_SI5351:
|
||||||
return portapack::clock_generator.read_register(register_number);
|
return portapack::clock_generator.read_register(register_number);
|
||||||
case CT_BATTERY:
|
case CT_MAX17055: {
|
||||||
return battery::BatteryManagement::read_register(register_number);
|
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
|
return dev->read_register(register_number);
|
||||||
|
}
|
||||||
case CT_AUDIO:
|
case CT_AUDIO:
|
||||||
return audio::debug::reg_read(register_number);
|
return audio::debug::reg_read(register_number);
|
||||||
}
|
}
|
||||||
@ -249,9 +167,11 @@ void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t v
|
|||||||
case CT_SI5351:
|
case CT_SI5351:
|
||||||
portapack::clock_generator.write_register(register_number, value);
|
portapack::clock_generator.write_register(register_number, value);
|
||||||
break;
|
break;
|
||||||
case CT_BATTERY:
|
case CT_MAX17055: {
|
||||||
battery::BatteryManagement::write_register(register_number, value);
|
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
|
dev->write_register(register_number, value);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case CT_AUDIO:
|
case CT_AUDIO:
|
||||||
audio::debug::reg_write(register_number, value);
|
audio::debug::reg_write(register_number, value);
|
||||||
break;
|
break;
|
||||||
@ -465,9 +385,9 @@ void DebugPeripheralsMenuView::on_populate() {
|
|||||||
{si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
{si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
||||||
{audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
{audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||||
});
|
});
|
||||||
if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
|
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
|
||||||
add_item(
|
add_item(
|
||||||
{"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("MAX17055", RegistersWidgetConfig{CT_BATTERY, 256, 16, 16}); }});
|
{"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("MAX17055", RegistersWidgetConfig{CT_MAX17055, 256, 16, 16}); }});
|
||||||
}
|
}
|
||||||
set_max_rows(2); // allow wider buttons
|
set_max_rows(2); // allow wider buttons
|
||||||
}
|
}
|
||||||
@ -502,17 +422,15 @@ void DebugMenuView::on_populate() {
|
|||||||
{"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
|
{"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(); }},
|
{"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 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>(); }},
|
{"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>(); }},
|
{"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>(); }},
|
{"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>(); }},
|
{"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>(); }},
|
{"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>(); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
|
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
|
||||||
add_item(
|
add_item(
|
||||||
{"Battery", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_batt_icon, [this]() { nav_.push<BatteryCapacityView>(); }});
|
{"Battery", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_batt_icon, [this]() { nav_.push<BatteryCapacityView>(); }});
|
||||||
}
|
}
|
||||||
|
@ -86,60 +86,13 @@ class DebugMemoryView : public View {
|
|||||||
"Done"};
|
"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 {
|
typedef enum {
|
||||||
CT_PMEM,
|
CT_PMEM,
|
||||||
CT_RFFC5072,
|
CT_RFFC5072,
|
||||||
CT_MAX283X,
|
CT_MAX283X,
|
||||||
CT_SI5351,
|
CT_SI5351,
|
||||||
CT_AUDIO,
|
CT_AUDIO,
|
||||||
CT_BATTERY,
|
CT_MAX17055,
|
||||||
} chip_type_t;
|
} chip_type_t;
|
||||||
|
|
||||||
struct RegistersWidgetConfig {
|
struct RegistersWidgetConfig {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
#include "ui_debug_battery.hpp"
|
#include "ui_debug_max17055.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index) {
|
BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index) {
|
||||||
if (index < battery::max17055::MAX17055::entries_count) {
|
if (index < i2cdev::I2cDev_MAX17055::entries_count) {
|
||||||
return battery::max17055::MAX17055::entries[index];
|
return i2cdev::I2cDev_MAX17055::entries[index];
|
||||||
}
|
}
|
||||||
return {"", 0, "", 0, false, "", false, 0, false, false, false, 0, false};
|
return {"", 0, "", 0, false, "", false, 0, false, false, false, 0, false};
|
||||||
}
|
}
|
||||||
@ -27,6 +27,12 @@ BatteryCapacityView::BatteryCapacityView(NavigationView& nav) {
|
|||||||
|
|
||||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||||
|
|
||||||
|
auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
|
if (!dev) { // dev not found
|
||||||
|
nav.pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
populate_page(0);
|
populate_page(0);
|
||||||
update_page_text();
|
update_page_text();
|
||||||
}
|
}
|
||||||
@ -37,7 +43,7 @@ void BatteryCapacityView::focus() {
|
|||||||
|
|
||||||
bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
|
bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
|
||||||
int32_t new_page = current_page + delta;
|
int32_t new_page = current_page + delta;
|
||||||
if (new_page >= 0 && new_page < ((int32_t)battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE) {
|
if (new_page >= 0 && new_page < ((int32_t)i2cdev::I2cDev_MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE) {
|
||||||
current_page = new_page;
|
current_page = new_page;
|
||||||
populate_page(current_page * ENTRIES_PER_PAGE);
|
populate_page(current_page * ENTRIES_PER_PAGE);
|
||||||
update_page_text();
|
update_page_text();
|
||||||
@ -46,11 +52,12 @@ bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BatteryCapacityView::update_values() {
|
void BatteryCapacityView::update_values() {
|
||||||
|
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
||||||
size_t entry_index = current_page * ENTRIES_PER_PAGE + i;
|
size_t entry_index = current_page * ENTRIES_PER_PAGE + i;
|
||||||
if (entry_index < battery::max17055::MAX17055::entries_count) {
|
if (entry_index < i2cdev::I2cDev_MAX17055::entries_count) {
|
||||||
const auto entry = get_entry(entry_index);
|
const auto entry = get_entry(entry_index);
|
||||||
uint16_t raw_value = battery::BatteryManagement::read_register(entry.address);
|
uint16_t raw_value = dev->read_register(entry.address);
|
||||||
|
|
||||||
hex_texts[i].set("0x" + to_string_hex(raw_value, 4));
|
hex_texts[i].set("0x" + to_string_hex(raw_value, 4));
|
||||||
|
|
||||||
@ -78,7 +85,7 @@ void BatteryCapacityView::update_values() {
|
|||||||
void BatteryCapacityView::populate_page(int start_index) {
|
void BatteryCapacityView::populate_page(int start_index) {
|
||||||
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
||||||
size_t entry_index = start_index + i;
|
size_t entry_index = start_index + i;
|
||||||
if (entry_index < battery::max17055::MAX17055::entries_count) {
|
if (entry_index < i2cdev::I2cDev_MAX17055::entries_count) {
|
||||||
const auto entry = get_entry(entry_index);
|
const auto entry = get_entry(entry_index);
|
||||||
name_texts[i].set(entry.name);
|
name_texts[i].set(entry.name);
|
||||||
addr_texts[i].set("0x" + to_string_hex(entry.address, 2));
|
addr_texts[i].set("0x" + to_string_hex(entry.address, 2));
|
||||||
@ -97,7 +104,7 @@ void BatteryCapacityView::populate_page(int start_index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BatteryCapacityView::update_page_text() {
|
void BatteryCapacityView::update_page_text() {
|
||||||
int total_pages = (battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
|
int total_pages = (i2cdev::I2cDev_MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
|
||||||
page_text.set("Page " + to_string_dec_uint(current_page + 1) + "/" + to_string_dec_uint(total_pages));
|
page_text.set("Page " + to_string_dec_uint(current_page + 1) + "/" + to_string_dec_uint(total_pages));
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,8 @@
|
|||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "battery.hpp"
|
#include "battery.hpp"
|
||||||
#include "max17055.hpp"
|
#include "i2cdevmanager.hpp"
|
||||||
|
#include "i2cdev_max17055.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ class BatteryCapacityView : public View {
|
|||||||
|
|
||||||
bool on_encoder(const EncoderEvent delta) override;
|
bool on_encoder(const EncoderEvent delta) override;
|
||||||
|
|
||||||
using RegisterEntry = battery::max17055::RegisterEntry;
|
using RegisterEntry = i2cdev::I2cDev_MAX17055::RegisterEntry;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static RegisterEntry get_entry(size_t index);
|
static RegisterEntry get_entry(size_t index);
|
@ -41,11 +41,6 @@ DfuMenu::DfuMenu(NavigationView& nav)
|
|||||||
&text_info_line_8,
|
&text_info_line_8,
|
||||||
&text_info_line_9,
|
&text_info_line_9,
|
||||||
&text_info_line_10});
|
&text_info_line_10});
|
||||||
|
|
||||||
if (battery::BatteryManagement::isDetected()) {
|
|
||||||
add_child(&voltage_label);
|
|
||||||
add_child(&text_info_line_11);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DfuMenu::paint(Painter& painter) {
|
void DfuMenu::paint(Painter& painter) {
|
||||||
@ -53,7 +48,7 @@ void DfuMenu::paint(Painter& painter) {
|
|||||||
size_t m0_fragmented_free_space = 0;
|
size_t m0_fragmented_free_space = 0;
|
||||||
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
||||||
|
|
||||||
auto lines = (battery::BatteryManagement::isDetected() ? 11 : 10) + 2;
|
auto lines = 10 + 2;
|
||||||
|
|
||||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||||
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
||||||
@ -65,9 +60,6 @@ void DfuMenu::paint(Painter& painter) {
|
|||||||
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||||
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||||
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||||
if (battery::BatteryManagement::isDetected()) {
|
|
||||||
text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto margin = 5;
|
constexpr auto margin = 5;
|
||||||
|
|
||||||
|
@ -60,8 +60,6 @@ class DfuMenu : public View {
|
|||||||
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Theme::getInstance()->fg_darkcyan->foreground},
|
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Theme::getInstance()->fg_darkcyan->foreground}};
|
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Theme::getInstance()->fg_darkcyan->foreground}};
|
||||||
|
|
||||||
Labels voltage_label{{{6 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "Voltage:", Theme::getInstance()->fg_darkcyan->foreground}};
|
|
||||||
|
|
||||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
@ -72,7 +70,6 @@ class DfuMenu : public View {
|
|||||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_11{{15 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class DfuMenu2 : public View {
|
class DfuMenu2 : public View {
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
#define PADDING_LEFT 1
|
||||||
|
#define PADDING_RIGHT 1
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@ -58,6 +61,7 @@ EncodersConfigView::EncodersConfigView(
|
|||||||
|
|
||||||
options_enctype.on_change = [this](size_t index, int32_t) {
|
options_enctype.on_change = [this](size_t index, int32_t) {
|
||||||
on_type_change(index);
|
on_type_change(index);
|
||||||
|
set_dirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
options_enctype.set_options(enc_options);
|
options_enctype.set_options(enc_options);
|
||||||
@ -104,7 +108,18 @@ void EncodersConfigView::on_type_change(size_t index) {
|
|||||||
|
|
||||||
// Add new SymFields.
|
// Add new SymFields.
|
||||||
Point pos{2 * 8, 9 * 8};
|
Point pos{2 * 8, 9 * 8};
|
||||||
std::string format_string;
|
std::string format_string = encoder_def->word_format;
|
||||||
|
|
||||||
|
/*important notes of this format_String
|
||||||
|
* previously before PR#1444, this code using the for loop below to generate the format_string, which MAYBE to prevent showing the S symble.
|
||||||
|
* but after PR#1444, the entireh of symfield code not compatible with this design anymore.
|
||||||
|
* however the encoder_def struct itself has a field called word_format, which exactly be avle to use as the format_string,
|
||||||
|
* because itself is ONLY a hint text, not impact protocol things.
|
||||||
|
* When you find OOK protocol is broken and see this commit from git blame, please be aware that it's not from here.
|
||||||
|
* previously me and @cusspvz already found it broken, this commit is just to fix the broken that from PR#1444.
|
||||||
|
* @zxkmm
|
||||||
|
*/
|
||||||
|
|
||||||
uint8_t word_length = encoder_def->word_length;
|
uint8_t word_length = encoder_def->word_length;
|
||||||
auto on_change_handler = [this](SymField&) {
|
auto on_change_handler = [this](SymField&) {
|
||||||
generate_frame();
|
generate_frame();
|
||||||
@ -119,11 +134,9 @@ void EncodersConfigView::on_type_change(size_t index) {
|
|||||||
switch (symbol_type) {
|
switch (symbol_type) {
|
||||||
case 'A':
|
case 'A':
|
||||||
symfield->set_symbol_list(encoder_def->address_symbols);
|
symfield->set_symbol_list(encoder_def->address_symbols);
|
||||||
format_string += 'A';
|
|
||||||
break;
|
break;
|
||||||
case 'D':
|
case 'D':
|
||||||
symfield->set_symbol_list(encoder_def->data_symbols);
|
symfield->set_symbol_list(encoder_def->data_symbols);
|
||||||
format_string += 'D';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +144,8 @@ void EncodersConfigView::on_type_change(size_t index) {
|
|||||||
pos += Point{8, 0};
|
pos += Point{8, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ugly :( Pad to erase
|
// cut the S, cuz sync bit isn't in symfield for user to chage/edit.
|
||||||
format_string.append(24 - format_string.size(), ' ');
|
format_string.erase(std::remove(format_string.begin(), format_string.end(), 'S'), format_string.end());
|
||||||
text_format.set(format_string);
|
text_format.set(format_string);
|
||||||
|
|
||||||
generate_frame();
|
generate_frame();
|
||||||
@ -144,12 +157,34 @@ void EncodersConfigView::on_show() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EncodersConfigView::draw_waveform() {
|
void EncodersConfigView::draw_waveform() {
|
||||||
|
// padding reason:
|
||||||
|
// in real world the signal would always start with low level and became low level again after yout turn off the radio;
|
||||||
|
// the waveform_buffer only controls drawing, the real send logic that been sent is controlled by frame_fragments
|
||||||
|
// so just for out of looking things
|
||||||
|
|
||||||
size_t length = frame_fragments.length();
|
size_t length = frame_fragments.length();
|
||||||
|
|
||||||
for (size_t n = 0; n < length; n++)
|
// currently not needed since all the supported OOK protocol wont exceed 550 yet
|
||||||
waveform_buffer[n] = (frame_fragments[n] == '0') ? 0 : 1;
|
if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) {
|
||||||
|
length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
waveform.set_length(length);
|
// padding l
|
||||||
|
for (size_t i = 0; i < PADDING_LEFT; i++) {
|
||||||
|
waveform_buffer[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// real wf
|
||||||
|
for (size_t n = 0; n < length; n++) {
|
||||||
|
waveform_buffer[n + PADDING_LEFT] = (frame_fragments[n] == '0') ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// padding r
|
||||||
|
for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) {
|
||||||
|
waveform_buffer[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT);
|
||||||
waveform.set_dirty();
|
waveform.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define WAVEFORM_BUFFER_SIZE 550
|
||||||
|
|
||||||
using namespace encoders;
|
using namespace encoders;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@ -56,7 +58,7 @@ class EncodersConfigView : public View {
|
|||||||
std::string frame_fragments = "0";
|
std::string frame_fragments = "0";
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int16_t waveform_buffer[550];
|
int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE];
|
||||||
const encoder_def_t* encoder_def{};
|
const encoder_def_t* encoder_def{};
|
||||||
|
|
||||||
void draw_waveform();
|
void draw_waveform();
|
||||||
|
122
firmware/application/apps/ui_external_module_view.cpp
Normal file
122
firmware/application/apps/ui_external_module_view.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Bernd Herzog
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui_external_module_view.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "ui_standalone_view.hpp"
|
||||||
|
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
#include "i2cdev_ppmod.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
void ExternalModuleView::focus() {
|
||||||
|
dummy.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExternalModuleView::on_tick_second() {
|
||||||
|
i2cdev::I2CDevManager::manual_scan();
|
||||||
|
|
||||||
|
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||||
|
|
||||||
|
if (!dev) {
|
||||||
|
text_header.set("No module connected");
|
||||||
|
text_name.set("");
|
||||||
|
text_version.set("");
|
||||||
|
text_number_apps.set("");
|
||||||
|
text_app1_name.set("");
|
||||||
|
text_app2_name.set("");
|
||||||
|
text_app3_name.set("");
|
||||||
|
text_app4_name.set("");
|
||||||
|
text_app5_name.set("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto device_info = dev->readDeviceInfo();
|
||||||
|
|
||||||
|
if (device_info.has_value() == false) {
|
||||||
|
text_header.set("No module connected");
|
||||||
|
text_name.set("");
|
||||||
|
text_version.set("");
|
||||||
|
text_number_apps.set("");
|
||||||
|
text_app1_name.set("");
|
||||||
|
text_app2_name.set("");
|
||||||
|
text_app3_name.set("");
|
||||||
|
text_app4_name.set("");
|
||||||
|
text_app5_name.set("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
text_header.set("Module found");
|
||||||
|
|
||||||
|
std::string btnText = (std::string) "Module: " + device_info->module_name;
|
||||||
|
text_name.set(btnText);
|
||||||
|
text_version.set("Version: " + std::to_string(device_info->module_version));
|
||||||
|
text_number_apps.set("No# Apps: " + std::to_string(device_info->application_count));
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < device_info->application_count && i < 5; i++) {
|
||||||
|
auto appInfo = dev->getStandaloneAppInfo(i);
|
||||||
|
if (appInfo.has_value() == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name;
|
||||||
|
|
||||||
|
switch (appInfo->menu_location) {
|
||||||
|
case app_location_t::UTILITIES:
|
||||||
|
btnText += " (Utilities)";
|
||||||
|
break;
|
||||||
|
case app_location_t::RX:
|
||||||
|
btnText += " (RX)";
|
||||||
|
break;
|
||||||
|
case app_location_t::TX:
|
||||||
|
btnText += " (TX)";
|
||||||
|
break;
|
||||||
|
case app_location_t::DEBUG:
|
||||||
|
btnText += " (Debug)";
|
||||||
|
break;
|
||||||
|
case app_location_t::HOME:
|
||||||
|
btnText += " (Home)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
text_app1_name.set(btnText);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
text_app2_name.set(btnText);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
text_app3_name.set(btnText);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
text_app4_name.set(btnText);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
text_app5_name.set(btnText);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ui
|
97
firmware/application/apps/ui_external_module_view.hpp
Normal file
97
firmware/application/apps/ui_external_module_view.hpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Bernd Herzog
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UI_EXTERNAL_MODULE_VIEW_H
|
||||||
|
#define __UI_EXTERNAL_MODULE_VIEW_H
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_painter.hpp"
|
||||||
|
#include "ui_menu.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
|
||||||
|
#include "rffc507x.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "memory_map.hpp"
|
||||||
|
#include "irq_controls.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
#include "i2cdev_ppmod.hpp"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class ExternalModuleView : public View {
|
||||||
|
public:
|
||||||
|
ExternalModuleView(NavigationView& nav)
|
||||||
|
: nav_(nav) {
|
||||||
|
add_children({&text_header,
|
||||||
|
&text_name,
|
||||||
|
&text_version,
|
||||||
|
&text_number_apps,
|
||||||
|
&text_app1_name,
|
||||||
|
&text_app2_name,
|
||||||
|
&text_app3_name,
|
||||||
|
&text_app4_name,
|
||||||
|
&text_app5_name,
|
||||||
|
&dummy});
|
||||||
|
|
||||||
|
text_header.set("No module connected");
|
||||||
|
|
||||||
|
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||||
|
on_tick_second();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
~ExternalModuleView() {
|
||||||
|
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string title() const override { return "Ext Module"; };
|
||||||
|
void focus() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NavigationView& nav_;
|
||||||
|
Text text_header{{16, 16, 208, 16}};
|
||||||
|
Text text_name{{24, 32, 200, 16}};
|
||||||
|
Text text_version{{24, 48, 200, 16}};
|
||||||
|
Text text_number_apps{{24, 64, 200, 16}};
|
||||||
|
|
||||||
|
Text text_app1_name{{24, 96, 200, 16}};
|
||||||
|
Text text_app2_name{{24, 112, 200, 16}};
|
||||||
|
Text text_app3_name{{24, 128, 200, 16}};
|
||||||
|
Text text_app4_name{{24, 144, 200, 16}};
|
||||||
|
Text text_app5_name{{24, 160, 200, 16}};
|
||||||
|
|
||||||
|
Button dummy{
|
||||||
|
{240, 0, 0, 0},
|
||||||
|
""};
|
||||||
|
|
||||||
|
SignalToken signal_token_tick_second{};
|
||||||
|
|
||||||
|
void on_tick_second();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
#endif
|
@ -27,7 +27,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
#include "ui_playlist.hpp"
|
#include "ui_playlist.hpp"
|
||||||
#include "ui_remote.hpp"
|
|
||||||
#include "ui_ss_viewer.hpp"
|
#include "ui_ss_viewer.hpp"
|
||||||
#include "ui_bmp_file_viewer.hpp"
|
#include "ui_bmp_file_viewer.hpp"
|
||||||
#include "ui_text_editor.hpp"
|
#include "ui_text_editor.hpp"
|
||||||
@ -704,10 +703,11 @@ bool FileManagerView::handle_file_open() {
|
|||||||
|
|
||||||
reload_current(false);
|
reload_current(false);
|
||||||
return true;
|
return true;
|
||||||
} else if (path_iequal(rem_ext, ext)) {
|
}
|
||||||
|
/*else if (path_iequal(rem_ext, ext)) {
|
||||||
nav_.push<RemoteView>(path);
|
nav_.push<RemoteView>(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -751,10 +751,17 @@ FileManagerView::FileManagerView(
|
|||||||
text_date.set("Too many files!");
|
text_date.set("Too many files!");
|
||||||
} else {
|
} else {
|
||||||
text_date.set_style(Theme::getInstance()->fg_medium);
|
text_date.set_style(Theme::getInstance()->fg_medium);
|
||||||
if (selected_is_valid())
|
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())));
|
if (get_selected_entry().path == str_back) {
|
||||||
else
|
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("");
|
text_date.set("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
|||||||
|
|
||||||
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
||||||
color,
|
color,
|
||||||
&bitmap_icon_temperature,
|
&bitmap_icon_peripherals_details,
|
||||||
[this, path](KeyEvent) {
|
[this, path](KeyEvent) {
|
||||||
this->firmware_selected(path);
|
this->firmware_selected(path);
|
||||||
}});
|
}});
|
||||||
|
@ -40,27 +40,39 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
// TODO: Clean up after moving to better lookup tables.
|
// TODO: Clean up after moving to better lookup tables.
|
||||||
using options_t = OptionsField::options_t;
|
using options_t = OptionsField::options_t;
|
||||||
extern options_t freqman_modulations;
|
|
||||||
extern options_t freqman_bandwidths[4];
|
using option_db_t = std::pair<std::string_view, int32_t>;
|
||||||
extern options_t freqman_steps;
|
using options_db_t = std::vector<option_db_t>;
|
||||||
extern options_t freqman_steps_short;
|
|
||||||
|
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. */
|
/* Set options. */
|
||||||
void freqman_set_modulation_option(OptionsField& option) {
|
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) {
|
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||||
if (is_valid(modulation))
|
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) {
|
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) {
|
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 {
|
namespace ui {
|
||||||
@ -438,7 +450,7 @@ void FrequencyEditView::populate_bandwidth_options() {
|
|||||||
auto& bandwidths = freqman_bandwidths[entry_.modulation];
|
auto& bandwidths = freqman_bandwidths[entry_.modulation];
|
||||||
for (auto i = 0u; i < bandwidths.size(); ++i) {
|
for (auto i = 0u; i < bandwidths.size(); ++i) {
|
||||||
auto& item = bandwidths[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) {
|
for (auto i = 0u; i < freqman_steps.size(); ++i) {
|
||||||
auto& item = freqman_steps[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));
|
field_step.set_options(std::move(options));
|
||||||
|
@ -1,198 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui_fsk_rx.hpp"
|
|
||||||
|
|
||||||
#include "audio.hpp"
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
|
||||||
#include "string_format.hpp"
|
|
||||||
#include "utility.hpp"
|
|
||||||
#include "file_path.hpp"
|
|
||||||
|
|
||||||
#include "ui_freqman.hpp"
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
namespace pmem = portapack::persistent_memory;
|
|
||||||
|
|
||||||
void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
|
|
||||||
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
|
|
||||||
|
|
||||||
// // Raw hex dump of all the codewords
|
|
||||||
// for (size_t c = 0; c < 16; c++)
|
|
||||||
// entry += to_string_hex(packet[c], 8) + " ";
|
|
||||||
|
|
||||||
log_file.write_entry(data + entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
|
|
||||||
log_file.write_entry(timestamp, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
// Console View
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
|
|
||||||
: View(parent_rect), nav_{nav} {
|
|
||||||
add_child(&console);
|
|
||||||
};
|
|
||||||
|
|
||||||
void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
|
|
||||||
if (is_data) {
|
|
||||||
console.write(to_string_dec_uint(value) + " ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxAppConsoleView::on_show() {
|
|
||||||
hidden(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxAppConsoleView::on_hide() {
|
|
||||||
hidden(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FskRxAppConsoleView::~FskRxAppConsoleView() {
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
// Spectrum View
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
|
|
||||||
: View(parent_rect), nav_{nav} {
|
|
||||||
add_child(&waterfall);
|
|
||||||
hidden(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FskRxAppView::~FskRxAppView() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxAppView::focus() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxAppView::on_show() {
|
|
||||||
hidden(false);
|
|
||||||
waterfall.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskRxAppView::on_hide() {
|
|
||||||
hidden(true);
|
|
||||||
waterfall.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
// Base View
|
|
||||||
//---------------------------------------------------------------------------------------------------------------
|
|
||||||
FskxRxMainView::FskxRxMainView(NavigationView& nav)
|
|
||||||
: nav_{nav} {
|
|
||||||
add_children({&tab_view,
|
|
||||||
&view_data,
|
|
||||||
&view_stream,
|
|
||||||
&labels,
|
|
||||||
&rssi,
|
|
||||||
&channel,
|
|
||||||
&field_rf_amp,
|
|
||||||
&field_lna,
|
|
||||||
&field_vga,
|
|
||||||
&field_frequency,
|
|
||||||
&deviation_frequency,
|
|
||||||
&record_view});
|
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_fskrx);
|
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
record_view.on_error = [&nav](std::string message) {
|
|
||||||
nav.display_modal("Error", message);
|
|
||||||
};
|
|
||||||
|
|
||||||
deviation_frequency.on_change = [this](rf::Frequency f) {
|
|
||||||
refresh_ui(f);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set initial sampling rate
|
|
||||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
|
||||||
record_view.set_sampling_rate(initial_deviation * 2);
|
|
||||||
|
|
||||||
field_frequency.set_value(initial_target_frequency);
|
|
||||||
deviation_frequency.set_value(initial_deviation);
|
|
||||||
|
|
||||||
logger.append(logs_dir / u"FSKRX.TXT");
|
|
||||||
|
|
||||||
baseband::set_fsk(initial_deviation);
|
|
||||||
|
|
||||||
audio::output::start();
|
|
||||||
receiver_model.enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
|
|
||||||
if (logging()) {
|
|
||||||
logger.log_decoded(timestamp, prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
|
|
||||||
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
|
||||||
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
|
||||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
|
||||||
auto sample_rate = deviationHz * 2;
|
|
||||||
|
|
||||||
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
|
||||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
|
||||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
|
||||||
|
|
||||||
if (!view_stream.hidden()) {
|
|
||||||
view_stream.waterfall.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
|
||||||
// NB: record_view is what actually updates proc_capture baseband settings.
|
|
||||||
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
|
||||||
|
|
||||||
// Update the radio model with the actual sampling rate.
|
|
||||||
receiver_model.set_sampling_rate(actual_sample_rate);
|
|
||||||
|
|
||||||
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
|
|
||||||
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
|
||||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
|
||||||
|
|
||||||
if (!view_stream.hidden()) {
|
|
||||||
view_stream.waterfall.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskxRxMainView::focus() {
|
|
||||||
field_frequency.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
|
|
||||||
View::set_parent_rect(new_parent_rect);
|
|
||||||
|
|
||||||
ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
|
||||||
view_stream.waterfall.set_parent_rect(waterfall_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
FskxRxMainView::~FskxRxMainView() {
|
|
||||||
audio::output::stop();
|
|
||||||
receiver_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
} /* namespace ui */
|
|
@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2018 Furrtek
|
|
||||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __UI_FSK_RX_H__
|
|
||||||
#define __UI_FSK_RX_H__
|
|
||||||
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_freq_field.hpp"
|
|
||||||
#include "ui_receiver.hpp"
|
|
||||||
#include "ui_record_view.hpp"
|
|
||||||
#include "ui_rssi.hpp"
|
|
||||||
#include "ui_spectrum.hpp"
|
|
||||||
#include "ui_tabview.hpp"
|
|
||||||
|
|
||||||
#include "app_settings.hpp"
|
|
||||||
#include "log_file.hpp"
|
|
||||||
#include "radio_state.hpp"
|
|
||||||
#include "pocsag_app.hpp"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
class FskRxLogger {
|
|
||||||
public:
|
|
||||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
|
||||||
return log_file.append(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_raw_data(const std::string& data, const uint32_t frequency);
|
|
||||||
void log_decoded(Timestamp timestamp, const std::string& text);
|
|
||||||
|
|
||||||
private:
|
|
||||||
LogFile log_file{};
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
class FskRxAppConsoleView : public View {
|
|
||||||
public:
|
|
||||||
FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
|
|
||||||
~FskRxAppConsoleView();
|
|
||||||
|
|
||||||
std::string title() const override { return "FSK RX Data"; };
|
|
||||||
|
|
||||||
void on_packet(uint32_t value, bool is_data);
|
|
||||||
|
|
||||||
void on_show() override;
|
|
||||||
void on_hide() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
NavigationView& nav_;
|
|
||||||
|
|
||||||
Console console{
|
|
||||||
{0, 0, 240, 224}};
|
|
||||||
};
|
|
||||||
|
|
||||||
class FskRxAppView : public View {
|
|
||||||
public:
|
|
||||||
FskRxAppView(NavigationView& nav, Rect parent_rect);
|
|
||||||
~FskRxAppView();
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
void on_show() override;
|
|
||||||
void on_hide() override;
|
|
||||||
|
|
||||||
spectrum::WaterfallView waterfall{};
|
|
||||||
|
|
||||||
std::string title() const override { return "FSK RX Stream"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
NavigationView& nav_;
|
|
||||||
RxRadioState radio_state_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class FskxRxMainView : public View {
|
|
||||||
public:
|
|
||||||
FskxRxMainView(NavigationView& nav);
|
|
||||||
~FskxRxMainView();
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
void set_parent_rect(const Rect new_parent_rect) override;
|
|
||||||
|
|
||||||
std::string title() const override { return "FSK RX"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr uint32_t initial_target_frequency = 902'075'000;
|
|
||||||
static constexpr ui::Dim header_height = (5 * 16);
|
|
||||||
|
|
||||||
uint32_t initial_deviation{3750};
|
|
||||||
|
|
||||||
bool logging() const { return false; };
|
|
||||||
bool logging_raw() const { return false; };
|
|
||||||
|
|
||||||
NavigationView& nav_;
|
|
||||||
Rect view_rect = {0, header_height, 240, 224};
|
|
||||||
|
|
||||||
FskRxAppView view_stream{nav_, view_rect};
|
|
||||||
FskRxAppConsoleView view_data{nav_, view_rect};
|
|
||||||
|
|
||||||
TabView tab_view{
|
|
||||||
{"Data", Theme::getInstance()->fg_yellow->foreground, &view_data},
|
|
||||||
{"Stream", Theme::getInstance()->fg_cyan->foreground, &view_stream}};
|
|
||||||
|
|
||||||
void refresh_ui(rf::Frequency f);
|
|
||||||
void on_packet(uint32_t value, bool is_data);
|
|
||||||
void handle_decoded(Timestamp timestamp, const std::string& prefix);
|
|
||||||
|
|
||||||
uint32_t last_address = 0;
|
|
||||||
FskRxLogger logger{};
|
|
||||||
uint16_t packet_count = 0;
|
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
|
||||||
{0 * 8, 4 * 8},
|
|
||||||
nav_};
|
|
||||||
|
|
||||||
RFAmpField field_rf_amp{
|
|
||||||
{11 * 8, 2 * 16}};
|
|
||||||
|
|
||||||
LNAGainField field_lna{
|
|
||||||
{13 * 8, 2 * 16}};
|
|
||||||
|
|
||||||
VGAGainField field_vga{
|
|
||||||
{16 * 8, 2 * 16}};
|
|
||||||
|
|
||||||
RSSI rssi{
|
|
||||||
{19 * 8 - 4, 35, 6 * 8, 4}};
|
|
||||||
|
|
||||||
Channel channel{
|
|
||||||
{19 * 8 - 4, 40, 6 * 8, 4}};
|
|
||||||
|
|
||||||
Labels labels{
|
|
||||||
{{0 * 8, 3 * 16}, "Deviation:", Theme::getInstance()->fg_light->foreground},
|
|
||||||
};
|
|
||||||
|
|
||||||
FrequencyField deviation_frequency{
|
|
||||||
{10 * 8, 3 * 16},
|
|
||||||
{3750, 500000},
|
|
||||||
};
|
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
RecordView record_view{
|
|
||||||
{0 * 8, 4 * 16, 30 * 8, 1 * 16},
|
|
||||||
u"FSKRX_????.C16",
|
|
||||||
u"FSKRX",
|
|
||||||
RecordView::FileType::RawS16,
|
|
||||||
16384,
|
|
||||||
3};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
|
||||||
Message::ID::AFSKData,
|
|
||||||
[this](Message* const p) {
|
|
||||||
const auto message = static_cast<const AFSKDataMessage*>(p);
|
|
||||||
this->view_data.on_packet(message->value, message->is_data);
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__POCSAG_APP_H__*/
|
|
@ -187,6 +187,7 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
if (last_max_db != statistics.max_db) {
|
if (last_max_db != statistics.max_db) {
|
||||||
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");
|
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
|
||||||
|
rssi.set_db(statistics.max_db);
|
||||||
}
|
}
|
||||||
// refresh rssi
|
// 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()) {
|
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()) {
|
||||||
|
@ -98,7 +98,6 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
|
|||||||
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
|
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
|
||||||
} else
|
} else
|
||||||
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
|
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
|
||||||
|
|
||||||
return freq_at_pos;
|
return freq_at_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +256,7 @@ void GlassView::on_range_changed() {
|
|||||||
bin_length = SCREEN_W;
|
bin_length = SCREEN_W;
|
||||||
ignore_dc = 0;
|
ignore_dc = 0;
|
||||||
looking_glass_bandwidth = looking_glass_range;
|
looking_glass_bandwidth = looking_glass_range;
|
||||||
looking_glass_sampling_rate = looking_glass_bandwidth;
|
looking_glass_sampling_rate = looking_glass_range;
|
||||||
each_bin_size = looking_glass_bandwidth / SCREEN_W;
|
each_bin_size = looking_glass_bandwidth / SCREEN_W;
|
||||||
looking_glass_step = looking_glass_bandwidth;
|
looking_glass_step = looking_glass_bandwidth;
|
||||||
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
||||||
@ -265,7 +264,7 @@ void GlassView::on_range_changed() {
|
|||||||
// view is made in multiple pass, use original bin picking
|
// view is made in multiple pass, use original bin picking
|
||||||
mode = scan_type.selected_index_value();
|
mode = scan_type.selected_index_value();
|
||||||
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
|
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
|
||||||
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
|
looking_glass_sampling_rate = LOOKING_GLASS_MAX_SAMPLERATE;
|
||||||
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
|
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
|
||||||
if (mode == LOOKING_GLASS_FASTSCAN) {
|
if (mode == LOOKING_GLASS_FASTSCAN) {
|
||||||
offset = 2;
|
offset = 2;
|
||||||
@ -315,8 +314,8 @@ void GlassView::update_min(int32_t v) {
|
|||||||
int32_t min_size = steps;
|
int32_t min_size = steps;
|
||||||
if (locked_range)
|
if (locked_range)
|
||||||
min_size = search_span;
|
min_size = search_span;
|
||||||
if (min_size < 2)
|
if (min_size < 1)
|
||||||
min_size = 2;
|
min_size = 1;
|
||||||
if (v > 7200 - min_size) {
|
if (v > 7200 - min_size) {
|
||||||
v = 7200 - min_size;
|
v = 7200 - min_size;
|
||||||
}
|
}
|
||||||
@ -332,8 +331,8 @@ void GlassView::update_max(int32_t v) {
|
|||||||
int32_t min_size = steps;
|
int32_t min_size = steps;
|
||||||
if (locked_range)
|
if (locked_range)
|
||||||
min_size = search_span;
|
min_size = search_span;
|
||||||
if (min_size < 2)
|
if (min_size < 1)
|
||||||
min_size = 2;
|
min_size = 1;
|
||||||
if (v < min_size) {
|
if (v < min_size) {
|
||||||
v = min_size;
|
v = min_size;
|
||||||
}
|
}
|
||||||
@ -487,7 +486,12 @@ GlassView::GlassView(
|
|||||||
range_presets.set_selected_index(preset_index);
|
range_presets.set_selected_index(preset_index);
|
||||||
|
|
||||||
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
|
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
|
||||||
marker_pixel_index = clip<uint8_t>(marker_pixel_index + delta, 0, SCREEN_W);
|
if ((marker_pixel_index + delta) < 0)
|
||||||
|
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
|
||||||
|
else if ((marker_pixel_index + delta) > SCREEN_W)
|
||||||
|
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
|
||||||
|
else
|
||||||
|
marker_pixel_index = marker_pixel_index + delta;
|
||||||
on_marker_change();
|
on_marker_change();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
||||||
|
#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
|
||||||
#define MHZ_DIV 1000000
|
#define MHZ_DIV 1000000
|
||||||
|
|
||||||
// blanked DC (16 centered bins ignored ) and top left and right (2 bins ignored on each side )
|
// blanked DC (16 centered bins ignored ) and top left and right (2 bins ignored on each side )
|
||||||
|
@ -1,288 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui_numbers.hpp"
|
|
||||||
#include "string_format.hpp"
|
|
||||||
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "hackrf_hal.hpp"
|
|
||||||
#include "portapack_shared_memory.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable)
|
|
||||||
|
|
||||||
void NumbersStationView::focus() {
|
|
||||||
if (file_error)
|
|
||||||
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT);
|
|
||||||
else
|
|
||||||
button_exit.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
NumbersStationView::~NumbersStationView() {
|
|
||||||
transmitter_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
NumbersStationView::wav_file_t* NumbersStationView::get_wav(uint32_t index) {
|
|
||||||
return ¤t_voice->available_wavs[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void NumbersStationView::prepare_audio() {
|
|
||||||
uint8_t code;
|
|
||||||
wav_file_t* wav_file;
|
|
||||||
|
|
||||||
if (sample_counter >= sample_length) {
|
|
||||||
if (segment == ANNOUNCE) {
|
|
||||||
if (!announce_loop) {
|
|
||||||
code_index = 0;
|
|
||||||
segment = MESSAGE;
|
|
||||||
} else {
|
|
||||||
wav_file = get_wav(11);
|
|
||||||
reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav");
|
|
||||||
sample_length = wav_file->length;
|
|
||||||
announce_loop--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment == MESSAGE) {
|
|
||||||
if (code_index == 25) {
|
|
||||||
transmitter_model.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
code = symfield_code.get_offset(code_index);
|
|
||||||
|
|
||||||
if (code >= 10) {
|
|
||||||
memset(audio_buffer, 0, 1024);
|
|
||||||
if (code == 10) {
|
|
||||||
pause = 11025; // p: 0.25s @ 44100Hz
|
|
||||||
} else if (code == 11) {
|
|
||||||
pause = 33075; // P: 0.75s @ 44100Hz
|
|
||||||
} else if (code == 12) {
|
|
||||||
transmitter_model.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wav_file = get_wav(code);
|
|
||||||
reader->open(current_voice->dir + file_names[code].name + ".wav");
|
|
||||||
sample_length = wav_file->length;
|
|
||||||
}
|
|
||||||
code_index++;
|
|
||||||
}
|
|
||||||
sample_counter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pause) {
|
|
||||||
auto bytes_read = reader->read(audio_buffer, 1024).value();
|
|
||||||
|
|
||||||
// Unsigned to signed, pretty stupid :/
|
|
||||||
for (size_t n = 0; n < bytes_read; n++)
|
|
||||||
audio_buffer[n] -= 0x80;
|
|
||||||
for (size_t n = bytes_read; n < 1024; n++)
|
|
||||||
audio_buffer[n] = 0;
|
|
||||||
|
|
||||||
sample_counter += 1024;
|
|
||||||
} else {
|
|
||||||
if (pause >= 1024) {
|
|
||||||
pause -= 1024;
|
|
||||||
} else {
|
|
||||||
sample_counter = sample_length;
|
|
||||||
pause = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
baseband::set_fifo_data(audio_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NumbersStationView::start_tx() {
|
|
||||||
// sample_length = sound_sizes[10]; // Announce
|
|
||||||
sample_counter = sample_length;
|
|
||||||
|
|
||||||
code_index = 0;
|
|
||||||
announce_loop = 2;
|
|
||||||
segment = ANNOUNCE;
|
|
||||||
|
|
||||||
prepare_audio();
|
|
||||||
|
|
||||||
transmitter_model.set_rf_amp(true);
|
|
||||||
transmitter_model.enable();
|
|
||||||
|
|
||||||
baseband::set_audiotx_data(
|
|
||||||
(1536000 / 44100) - 1, // TODO: Read wav file's samplerate
|
|
||||||
12000,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NumbersStationView::on_tick_second() {
|
|
||||||
armed_blink = not armed_blink;
|
|
||||||
|
|
||||||
if (armed_blink)
|
|
||||||
check_armed.set_style(Theme::getInstance()->fg_red);
|
|
||||||
else
|
|
||||||
check_armed.set_style(&style());
|
|
||||||
|
|
||||||
check_armed.set_dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NumbersStationView::on_voice_changed(size_t index) {
|
|
||||||
std::string code_list;
|
|
||||||
|
|
||||||
for (const auto& wavs : voices[index].available_wavs)
|
|
||||||
code_list += wavs.code;
|
|
||||||
|
|
||||||
symfield_code.set_symbol_list(code_list);
|
|
||||||
current_voice = &voices[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) {
|
|
||||||
if (reader->open("/numbers/" + dir + "/" + file)) {
|
|
||||||
// Check format (mono, 8 bits)
|
|
||||||
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NumbersStationView::NumbersStationView(
|
|
||||||
NavigationView& nav)
|
|
||||||
: nav_(nav) {
|
|
||||||
std::vector<std::filesystem::path> directory_list;
|
|
||||||
using option_t = std::pair<std::string, int32_t>;
|
|
||||||
using options_t = std::vector<option_t>;
|
|
||||||
options_t voice_options;
|
|
||||||
voice_t temp_voice{};
|
|
||||||
bool valid;
|
|
||||||
uint32_t c;
|
|
||||||
// uint8_t y, m, d, dayofweek;
|
|
||||||
|
|
||||||
reader = std::make_unique<WAVFileReader>();
|
|
||||||
|
|
||||||
// Search for valid voice directories
|
|
||||||
directory_list = scan_root_directories("/numbers");
|
|
||||||
if (!directory_list.size()) {
|
|
||||||
file_error = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& dir : directory_list) {
|
|
||||||
c = 0;
|
|
||||||
for (const auto& file_name : file_names) {
|
|
||||||
valid = check_wav_validity(dir.string(), file_name.name + ".wav");
|
|
||||||
if ((!valid) && (file_name.required)) {
|
|
||||||
temp_voice.available_wavs.clear();
|
|
||||||
break; // Invalid required file means invalid voice
|
|
||||||
} else if (valid) {
|
|
||||||
temp_voice.available_wavs.push_back({file_name.code, c++, 0, 0}); // TODO: Needs length and samplerate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!temp_voice.available_wavs.empty()) {
|
|
||||||
// Voice can be used, are there accent files ?
|
|
||||||
c = 0;
|
|
||||||
for (const auto& file_name : file_names) {
|
|
||||||
valid = check_wav_validity(dir.string(), file_name.name + "a.wav");
|
|
||||||
if ((!valid) && (file_name.required)) {
|
|
||||||
c = 0;
|
|
||||||
break; // Invalid required file means accents can't be used
|
|
||||||
} else if (valid) {
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
temp_voice.accent = c ? true : false;
|
|
||||||
temp_voice.dir = dir.string();
|
|
||||||
|
|
||||||
voices.push_back(temp_voice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (voices.empty()) {
|
|
||||||
file_error = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
|
||||||
|
|
||||||
add_children({&labels,
|
|
||||||
&symfield_code,
|
|
||||||
&check_armed,
|
|
||||||
&options_voices,
|
|
||||||
&text_voice_flags,
|
|
||||||
//&button_tx_now,
|
|
||||||
&button_exit});
|
|
||||||
|
|
||||||
for (const auto& voice : voices)
|
|
||||||
voice_options.emplace_back(voice.dir.substr(0, 4), 0);
|
|
||||||
|
|
||||||
options_voices.set_options(voice_options);
|
|
||||||
options_voices.on_change = [this](size_t i, int32_t) {
|
|
||||||
this->on_voice_changed(i);
|
|
||||||
};
|
|
||||||
options_voices.set_selected_index(0);
|
|
||||||
|
|
||||||
check_armed.set_value(false);
|
|
||||||
|
|
||||||
check_armed.on_select = [this](Checkbox&, bool v) {
|
|
||||||
if (v) {
|
|
||||||
armed_blink = false;
|
|
||||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
|
||||||
this->on_tick_second();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
check_armed.set_style(&style());
|
|
||||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
symfield_code.set_offset(0, 10);
|
|
||||||
symfield_code.set_offset(1, 3);
|
|
||||||
symfield_code.set_offset(2, 4);
|
|
||||||
symfield_code.set_offset(3, 11);
|
|
||||||
symfield_code.set_offset(4, 6);
|
|
||||||
symfield_code.set_offset(5, 1);
|
|
||||||
symfield_code.set_offset(6, 9);
|
|
||||||
symfield_code.set_offset(7, 7);
|
|
||||||
symfield_code.set_offset(8, 8);
|
|
||||||
symfield_code.set_offset(9, 0);
|
|
||||||
symfield_code.set_offset(10, 12); // End
|
|
||||||
|
|
||||||
/*
|
|
||||||
dayofweek = rtc_time::current_day_of_week();
|
|
||||||
text_title.set(day_of_week[dayofweek]);
|
|
||||||
*/
|
|
||||||
|
|
||||||
button_exit.on_select = [&nav](Button&) {
|
|
||||||
nav.pop();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __UI_NUMBERS_H__
|
|
||||||
#define __UI_NUMBERS_H__
|
|
||||||
|
|
||||||
#include "ui.hpp"
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_receiver.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "rtc_time.hpp"
|
|
||||||
#include "clock_manager.hpp"
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "utility.hpp"
|
|
||||||
#include "message.hpp"
|
|
||||||
#include "file.hpp"
|
|
||||||
#include "io_wave.hpp"
|
|
||||||
#include "radio_state.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class NumbersStationView : public View {
|
|
||||||
public:
|
|
||||||
NumbersStationView(NavigationView& nav);
|
|
||||||
~NumbersStationView();
|
|
||||||
|
|
||||||
NumbersStationView(const NumbersStationView&) = delete;
|
|
||||||
NumbersStationView(NumbersStationView&&) = delete;
|
|
||||||
NumbersStationView& operator=(const NumbersStationView&) = delete;
|
|
||||||
NumbersStationView& operator=(NumbersStationView&&) = delete;
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Station"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
NavigationView& nav_;
|
|
||||||
|
|
||||||
TxRadioState radio_state_{
|
|
||||||
0 /* frequency */,
|
|
||||||
1750000 /* bandwidth */,
|
|
||||||
1536000 /* sampling rate */
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sequencing state machine
|
|
||||||
enum segments {
|
|
||||||
IDLE = 0,
|
|
||||||
ANNOUNCE,
|
|
||||||
MESSAGE,
|
|
||||||
SIGNOFF
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char code;
|
|
||||||
uint32_t index;
|
|
||||||
uint32_t length;
|
|
||||||
uint32_t samplerate;
|
|
||||||
} wav_file_t;
|
|
||||||
|
|
||||||
struct voice_t {
|
|
||||||
std::string dir;
|
|
||||||
std::vector<wav_file_t> available_wavs;
|
|
||||||
bool accent;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<voice_t> voices{};
|
|
||||||
voice_t* current_voice{};
|
|
||||||
|
|
||||||
struct wav_file_list_t {
|
|
||||||
std::string name;
|
|
||||||
bool required;
|
|
||||||
char code;
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<wav_file_list_t> file_names = {
|
|
||||||
{"0", true, '0'},
|
|
||||||
{"1", true, '1'},
|
|
||||||
{"2", true, '2'},
|
|
||||||
{"3", true, '3'},
|
|
||||||
{"4", true, '4'},
|
|
||||||
{"5", true, '5'},
|
|
||||||
{"6", true, '6'},
|
|
||||||
{"7", true, '7'},
|
|
||||||
{"8", true, '8'},
|
|
||||||
{"9", true, '9'},
|
|
||||||
{"announce", false, 'A'}};
|
|
||||||
|
|
||||||
segments segment{IDLE};
|
|
||||||
bool armed{false};
|
|
||||||
bool file_error{false};
|
|
||||||
|
|
||||||
// const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
|
|
||||||
// const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
|
|
||||||
|
|
||||||
std::unique_ptr<WAVFileReader> reader{};
|
|
||||||
|
|
||||||
uint8_t code_index{0}, announce_loop{0};
|
|
||||||
uint32_t sample_counter{0};
|
|
||||||
uint32_t sample_length{0};
|
|
||||||
int8_t audio_buffer[1024]{};
|
|
||||||
uint32_t pause{0};
|
|
||||||
bool armed_blink{false};
|
|
||||||
SignalToken signal_token_tick_second{};
|
|
||||||
|
|
||||||
wav_file_t* get_wav(uint32_t index);
|
|
||||||
bool check_wav_validity(const std::string dir, const std::string file);
|
|
||||||
void on_voice_changed(size_t index);
|
|
||||||
void on_tick_second();
|
|
||||||
void prepare_audio();
|
|
||||||
void start_tx();
|
|
||||||
|
|
||||||
Labels labels{
|
|
||||||
{{2 * 8, 5 * 8}, "Voice: Flags:", Theme::getInstance()->fg_light->foreground},
|
|
||||||
{{1 * 8, 8 * 8}, "Code:", Theme::getInstance()->fg_light->foreground}};
|
|
||||||
|
|
||||||
OptionsField options_voices{
|
|
||||||
{8 * 8, 1 * 8},
|
|
||||||
4,
|
|
||||||
{}};
|
|
||||||
Text text_voice_flags{
|
|
||||||
{19 * 8, 1 * 8, 2 * 8, 16},
|
|
||||||
""};
|
|
||||||
|
|
||||||
SymField symfield_code{
|
|
||||||
{1 * 8, 10 * 8},
|
|
||||||
25,
|
|
||||||
SymField::Type::Custom};
|
|
||||||
|
|
||||||
Checkbox check_armed{
|
|
||||||
{2 * 8, 13 * 16},
|
|
||||||
5,
|
|
||||||
"Armed"};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Button button_tx_now {
|
|
||||||
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
|
||||||
"TX now"};
|
|
||||||
*/
|
|
||||||
|
|
||||||
Button button_exit{
|
|
||||||
{21 * 8, 16 * 16, 64, 32},
|
|
||||||
"Exit"};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_fifo_signal{
|
|
||||||
Message::ID::RequestSignal,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
|
||||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
|
||||||
this->prepare_audio();
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__UI_NUMBERS_H__*/
|
|
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui_nuoptix.hpp"
|
|
||||||
|
|
||||||
#include "ch.h"
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "lfsr_random.hpp"
|
|
||||||
#include "string_format.hpp"
|
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
void NuoptixView::focus() {
|
|
||||||
number_timecode.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
NuoptixView::~NuoptixView() {
|
|
||||||
transmitter_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NuoptixView::on_tx_progress(const uint32_t progress, const bool done) {
|
|
||||||
if (done)
|
|
||||||
transmit(false);
|
|
||||||
else
|
|
||||||
progressbar.set_value(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NuoptixView::transmit(bool setup) {
|
|
||||||
uint8_t mod, tone_code;
|
|
||||||
uint8_t c;
|
|
||||||
uint8_t dtmf_message[6];
|
|
||||||
rtc::RTC datetime;
|
|
||||||
|
|
||||||
if (!tx_mode) {
|
|
||||||
transmitter_model.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx_mode == IMPROVISE)
|
|
||||||
timecode = lfsr_iterate(timecode) % 1999; // Could be 9999 but that would be one long audio track !
|
|
||||||
|
|
||||||
if (setup) {
|
|
||||||
progressbar.set_max(6 * 2);
|
|
||||||
|
|
||||||
if (tx_mode == IMPROVISE) {
|
|
||||||
// Seed from RTC
|
|
||||||
rtcGetTime(&RTCD1, &datetime);
|
|
||||||
timecode = datetime.day() + datetime.second();
|
|
||||||
} else {
|
|
||||||
timecode = number_timecode.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
transmitter_model.set_rf_amp(true);
|
|
||||||
transmitter_model.enable();
|
|
||||||
|
|
||||||
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
|
|
||||||
dtmf_message[1] = 'A'; // "Restart" method #1
|
|
||||||
} else {
|
|
||||||
dtmf_message[0] = '#';
|
|
||||||
dtmf_message[1] = (timecode / 1000) % 10;
|
|
||||||
chThdSleepMilliseconds(92); // 141-49ms
|
|
||||||
number_timecode.set_value(timecode);
|
|
||||||
}
|
|
||||||
|
|
||||||
progressbar.set_value(0);
|
|
||||||
|
|
||||||
dtmf_message[2] = (timecode / 100) % 10;
|
|
||||||
dtmf_message[3] = (timecode / 10) % 10;
|
|
||||||
dtmf_message[4] = timecode % 10;
|
|
||||||
|
|
||||||
mod = 0;
|
|
||||||
for (c = 1; c < 5; c++)
|
|
||||||
if (dtmf_message[c] <= 9)
|
|
||||||
mod += dtmf_message[c];
|
|
||||||
|
|
||||||
mod = 10 - (mod % 10);
|
|
||||||
if (mod == 10) mod = 0; // Is this right ?
|
|
||||||
|
|
||||||
text_mod.set("Mod: " + to_string_dec_uint(mod));
|
|
||||||
|
|
||||||
dtmf_message[5] = mod;
|
|
||||||
|
|
||||||
for (c = 0; c < 6; c++) {
|
|
||||||
tone_code = dtmf_message[c];
|
|
||||||
|
|
||||||
if (tone_code == 'A')
|
|
||||||
tone_code = 10;
|
|
||||||
else if (tone_code == 'B')
|
|
||||||
tone_code = 11;
|
|
||||||
else if (tone_code == 'C')
|
|
||||||
tone_code = 12;
|
|
||||||
else if (tone_code == 'D')
|
|
||||||
tone_code = 13;
|
|
||||||
else if (tone_code == '#')
|
|
||||||
tone_code = 14;
|
|
||||||
else if (tone_code == '*')
|
|
||||||
tone_code = 15;
|
|
||||||
|
|
||||||
shared_memory.bb_data.tones_data.message[c * 2] = tone_code;
|
|
||||||
shared_memory.bb_data.tones_data.message[c * 2 + 1] = 0xFF; // Silence
|
|
||||||
}
|
|
||||||
|
|
||||||
for (c = 0; c < 16; c++) {
|
|
||||||
baseband::set_tone(c * 2, dtmf_deltas[c][0], NUOPTIX_TONE_LENGTH);
|
|
||||||
baseband::set_tone(c * 2 + 1, dtmf_deltas[c][1], NUOPTIX_TONE_LENGTH);
|
|
||||||
}
|
|
||||||
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
|
|
||||||
|
|
||||||
audio::set_rate(audio::Rate::Hz_24000);
|
|
||||||
|
|
||||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
|
|
||||||
|
|
||||||
timecode++;
|
|
||||||
}
|
|
||||||
|
|
||||||
NuoptixView::NuoptixView(
|
|
||||||
NavigationView& nav) {
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_tones);
|
|
||||||
|
|
||||||
add_children({&number_timecode,
|
|
||||||
&text_timecode,
|
|
||||||
&text_mod,
|
|
||||||
&progressbar,
|
|
||||||
&tx_view});
|
|
||||||
|
|
||||||
number_timecode.set_value(1);
|
|
||||||
|
|
||||||
tx_view.on_edit_frequency = [this, &nav]() {
|
|
||||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
|
||||||
new_view->on_changed = [this](rf::Frequency f) {
|
|
||||||
transmitter_model.set_target_frequency(f);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
tx_view.on_start = [this]() {
|
|
||||||
tx_view.set_transmitting(true);
|
|
||||||
tx_mode = NORMAL;
|
|
||||||
transmit(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
tx_view.on_stop = [this]() {
|
|
||||||
tx_view.set_transmitting(false);
|
|
||||||
tx_mode = IDLE;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*button_impro.on_select = [this](Button&){
|
|
||||||
if (tx_mode == IMPROVISE) {
|
|
||||||
tx_mode = IDLE;
|
|
||||||
button_impro.set_text("IMPROVISE");
|
|
||||||
} else if (tx_mode == IDLE) {
|
|
||||||
tx_mode = IMPROVISE;
|
|
||||||
button_impro.set_text("STOP");
|
|
||||||
transmit(true);
|
|
||||||
}
|
|
||||||
};*/
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ui
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __UI_NUOPTIX_H__
|
|
||||||
#define __UI_NUOPTIX_H__
|
|
||||||
|
|
||||||
#include "ui.hpp"
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "ui_transmitter.hpp"
|
|
||||||
#include "rtc_time.hpp"
|
|
||||||
#include "tonesets.hpp"
|
|
||||||
#include "message.hpp"
|
|
||||||
#include "volume.hpp"
|
|
||||||
#include "audio.hpp"
|
|
||||||
#include "radio_state.hpp"
|
|
||||||
|
|
||||||
#define NUOPTIX_TONE_LENGTH ((TONES_SAMPLERATE * 0.049) - 1) // 49ms
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class NuoptixView : public View {
|
|
||||||
public:
|
|
||||||
NuoptixView(NavigationView& nav);
|
|
||||||
~NuoptixView();
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Nuoptix sync"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum tx_modes {
|
|
||||||
IDLE = 0,
|
|
||||||
NORMAL,
|
|
||||||
IMPROVISE
|
|
||||||
};
|
|
||||||
|
|
||||||
TxRadioState radio_state_{
|
|
||||||
0 /* frequency */,
|
|
||||||
1750000 /* bandwidth */,
|
|
||||||
1536000 /* sampling rate */
|
|
||||||
};
|
|
||||||
|
|
||||||
tx_modes tx_mode{IDLE};
|
|
||||||
|
|
||||||
void transmit(bool setup);
|
|
||||||
void on_tx_progress(const uint32_t progress, const bool done);
|
|
||||||
|
|
||||||
uint32_t timecode{0};
|
|
||||||
|
|
||||||
Text text_timecode{
|
|
||||||
{10 * 8, 2 * 16, 9 * 8, 16},
|
|
||||||
"Timecode:"};
|
|
||||||
|
|
||||||
NumberField number_timecode{
|
|
||||||
{13 * 8, 3 * 16},
|
|
||||||
4,
|
|
||||||
{1, 9999},
|
|
||||||
1,
|
|
||||||
'0'};
|
|
||||||
|
|
||||||
Text text_mod{
|
|
||||||
{10 * 8, 5 * 16, 6 * 8, 16},
|
|
||||||
"Mod: "};
|
|
||||||
|
|
||||||
ProgressBar progressbar{
|
|
||||||
{16, 14 * 16, 208, 16}};
|
|
||||||
|
|
||||||
/*Button button_impro {
|
|
||||||
{ 64, 184, 112, 40 },
|
|
||||||
"IMPROVISE"
|
|
||||||
};*/
|
|
||||||
|
|
||||||
TransmitterView tx_view{
|
|
||||||
16 * 16,
|
|
||||||
10000,
|
|
||||||
15};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_tx_progress{
|
|
||||||
Message::ID::TXProgress,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
|
||||||
this->on_tx_progress(message.progress, message.done);
|
|
||||||
}};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif /*__UI_NUOPTIX_H__*/
|
|
@ -33,6 +33,8 @@ using namespace pocsag;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
#define MAX_POCSAG_LENGTH 80
|
||||||
|
|
||||||
void POCSAGTXView::focus() {
|
void POCSAGTXView::focus() {
|
||||||
field_address.focus();
|
field_address.focus();
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ void POCSAGTXView::on_remote(const PocsagTosendMessage data) {
|
|||||||
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
|
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
|
||||||
field_address.set_value(data.addr);
|
field_address.set_value(data.addr);
|
||||||
message = (char*)data.msg;
|
message = (char*)data.msg;
|
||||||
|
buffer = message;
|
||||||
text_message.set(message);
|
text_message.set(message);
|
||||||
options_bitrate.dirty();
|
options_bitrate.dirty();
|
||||||
options_type.dirty();
|
options_type.dirty();
|
||||||
@ -105,9 +108,9 @@ bool POCSAGTXView::start_tx() {
|
|||||||
|
|
||||||
progressbar.set_max(total_frames);
|
progressbar.set_max(total_frames);
|
||||||
|
|
||||||
transmitter_model.set_rf_amp(true);
|
// transmitter_model.set_rf_amp(true);
|
||||||
transmitter_model.set_tx_gain(40);
|
// transmitter_model.set_tx_gain(40);
|
||||||
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
|
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
|
||||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
|
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
@ -139,11 +142,18 @@ bool POCSAGTXView::start_tx() {
|
|||||||
|
|
||||||
void POCSAGTXView::paint(Painter&) {
|
void POCSAGTXView::paint(Painter&) {
|
||||||
message = buffer;
|
message = buffer;
|
||||||
text_message.set(message);
|
text_message.set(message); // the whole message, but it may not fit.
|
||||||
|
if (message.length() > 30 && message.length() <= 60) {
|
||||||
|
text_message_l2.set(message.substr(29)); // remaining to 2nd line
|
||||||
|
} else if (message.length() > 60) {
|
||||||
|
text_message_l2.set(message.substr(29, 27) + "...");
|
||||||
|
} else {
|
||||||
|
text_message_l2.set("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGTXView::on_set_text(NavigationView& nav) {
|
void POCSAGTXView::on_set_text(NavigationView& nav) {
|
||||||
text_prompt(nav, buffer, 30);
|
text_prompt(nav, buffer, MAX_POCSAG_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
POCSAGTXView::POCSAGTXView(
|
POCSAGTXView::POCSAGTXView(
|
||||||
@ -158,6 +168,7 @@ POCSAGTXView::POCSAGTXView(
|
|||||||
&options_function,
|
&options_function,
|
||||||
&options_phase,
|
&options_phase,
|
||||||
&text_message,
|
&text_message,
|
||||||
|
&text_message_l2,
|
||||||
&button_message,
|
&button_message,
|
||||||
&progressbar,
|
&progressbar,
|
||||||
&tx_view});
|
&tx_view});
|
||||||
|
@ -122,15 +122,18 @@ class POCSAGTXView : public View {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
Text text_message{
|
Text text_message{
|
||||||
{0 * 8, 16 * 8, 16 * 8, 16},
|
{0 * 8, 16 * 8, 30 * 8, 16},
|
||||||
|
""};
|
||||||
|
Text text_message_l2{
|
||||||
|
{0 * 8, 18 * 8, 30 * 8, 16},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
Button button_message{
|
Button button_message{
|
||||||
{0 * 8, 18 * 8, 14 * 8, 32},
|
{0 * 8, 20 * 8, 14 * 8, 32},
|
||||||
"Set message"};
|
"Set message"};
|
||||||
|
|
||||||
ProgressBar progressbar{
|
ProgressBar progressbar{
|
||||||
{16, 200, 208, 16}};
|
{16, 210, 208, 16}};
|
||||||
|
|
||||||
TransmitterView tx_view{
|
TransmitterView tx_view{
|
||||||
16 * 16,
|
16 * 16,
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "file_reader.hpp"
|
#include "file_reader.hpp"
|
||||||
#include "tone_key.hpp"
|
#include "tone_key.hpp"
|
||||||
#include "replay_app.hpp"
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
#include "io_file.hpp"
|
#include "io_file.hpp"
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui_script.hpp"
|
|
||||||
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "event_m0.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
void ScriptView::on_frequency_select() {
|
|
||||||
// button_edit_freq.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptView::on_edit_freq(rf::Frequency f) {
|
|
||||||
(void)f;
|
|
||||||
// frequencies[menu_view.highlighted()].value = f;
|
|
||||||
setup_list();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptView::on_edit_desc(NavigationView& nav) {
|
|
||||||
(void)nav;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptView::on_delete() {
|
|
||||||
// frequencies.erase(frequencies.begin() + menu_view.highlighted());
|
|
||||||
setup_list();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptView::setup_list() {
|
|
||||||
// size_t n;
|
|
||||||
|
|
||||||
menu_view.clear();
|
|
||||||
|
|
||||||
/*for (n = 0; n < frequencies.size(); n++) {
|
|
||||||
menu_view.add_item({ freqman_item_string(frequencies[n]), Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ on_frequency_select(); } });
|
|
||||||
}*/
|
|
||||||
|
|
||||||
menu_view.set_parent_rect({0, 0, 240, 168});
|
|
||||||
menu_view.set_highlighted(menu_view.highlighted()); // Refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptView::focus() {
|
|
||||||
menu_view.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptView::ScriptView(
|
|
||||||
NavigationView& nav) {
|
|
||||||
add_children({&menu_view,
|
|
||||||
&text_edit,
|
|
||||||
&button_edit_freq,
|
|
||||||
&button_edit_desc,
|
|
||||||
&button_del,
|
|
||||||
&button_exit});
|
|
||||||
|
|
||||||
setup_list();
|
|
||||||
|
|
||||||
button_edit_freq.on_select = [this, &nav](Button&) {
|
|
||||||
/*auto new_view = nav.push<FrequencyKeypadView>(frequencies[menu_view.highlighted()].value);
|
|
||||||
new_view->on_changed = [this](rf::Frequency f) {
|
|
||||||
on_edit_freq(f);
|
|
||||||
};*/
|
|
||||||
};
|
|
||||||
|
|
||||||
button_edit_desc.on_select = [this, &nav](Button&) {
|
|
||||||
on_edit_desc(nav);
|
|
||||||
};
|
|
||||||
|
|
||||||
button_del.on_select = [this, &nav](Button&) {
|
|
||||||
nav.push<ModalMessageView>("Confirm", "Are you sure?", YESNO,
|
|
||||||
[this](bool choice) {
|
|
||||||
if (choice) {
|
|
||||||
on_delete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
button_exit.on_select = [this, &nav](Button&) {
|
|
||||||
nav.pop();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ui
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2016 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui.hpp"
|
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_painter.hpp"
|
|
||||||
#include "ui_menu.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "ui_receiver.hpp"
|
|
||||||
#include "ui_textentry.hpp"
|
|
||||||
#include "rtc_time.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
enum script_keyword {
|
|
||||||
STOP = 0,
|
|
||||||
WAIT_N,
|
|
||||||
WAIT_RTC,
|
|
||||||
IF,
|
|
||||||
LOOP,
|
|
||||||
END,
|
|
||||||
TX,
|
|
||||||
RX
|
|
||||||
};
|
|
||||||
|
|
||||||
struct script_line {
|
|
||||||
script_keyword keyword;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScriptView : public View {
|
|
||||||
public:
|
|
||||||
ScriptView(NavigationView& nav);
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Script editor"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
void on_frequency_select();
|
|
||||||
void on_edit_freq(rf::Frequency f);
|
|
||||||
void on_edit_desc(NavigationView& nav);
|
|
||||||
void on_delete();
|
|
||||||
void setup_list();
|
|
||||||
|
|
||||||
std::vector<script_line> script{};
|
|
||||||
|
|
||||||
MenuView menu_view{
|
|
||||||
{0, 0, 240, 168},
|
|
||||||
true};
|
|
||||||
|
|
||||||
Text text_edit{
|
|
||||||
{16, 194, 5 * 8, 16},
|
|
||||||
"Edit:"};
|
|
||||||
Button button_edit_freq{
|
|
||||||
{16, 194 + 16, 88, 32},
|
|
||||||
"Frequency"};
|
|
||||||
Button button_edit_desc{
|
|
||||||
{16, 194 + 16 + 34, 88, 32},
|
|
||||||
"Description"};
|
|
||||||
Button button_del{
|
|
||||||
{160, 192, 72, 64},
|
|
||||||
"Delete"};
|
|
||||||
|
|
||||||
Button button_exit{
|
|
||||||
{160, 264, 72, 32},
|
|
||||||
"Exit"};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -5,6 +5,7 @@
|
|||||||
* Copyright (C) 2023 Kyle Reed
|
* Copyright (C) 2023 Kyle Reed
|
||||||
* Copyright (C) 2024 Mark Thompson
|
* Copyright (C) 2024 Mark Thompson
|
||||||
* Copyright (C) 2024 u-foka
|
* Copyright (C) 2024 u-foka
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
@ -49,6 +50,8 @@ namespace fs = std::filesystem;
|
|||||||
#include "ui_font_fixed_8x16.hpp"
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
#include "cpld_update.hpp"
|
#include "cpld_update.hpp"
|
||||||
#include "config_mode.hpp"
|
#include "config_mode.hpp"
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
#include "i2cdev_max17055.hpp"
|
||||||
|
|
||||||
extern ui::SystemView* system_view_ptr;
|
extern ui::SystemView* system_view_ptr;
|
||||||
|
|
||||||
@ -321,7 +324,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
|||||||
&toggle_bias_tee,
|
&toggle_bias_tee,
|
||||||
&toggle_clock,
|
&toggle_clock,
|
||||||
&toggle_mute,
|
&toggle_mute,
|
||||||
&toggle_fake_brightness,
|
|
||||||
&toggle_sd_card,
|
&toggle_sd_card,
|
||||||
&button_save,
|
&button_save,
|
||||||
&button_cancel});
|
&button_cancel});
|
||||||
@ -359,7 +361,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
|||||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
|
||||||
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
||||||
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
||||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||||
@ -388,7 +389,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
|||||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
|
||||||
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
||||||
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
||||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||||
@ -777,20 +777,13 @@ void SetConfigModeView::focus() {
|
|||||||
/* SetDisplayView ************************************/
|
/* SetDisplayView ************************************/
|
||||||
|
|
||||||
SetDisplayView::SetDisplayView(NavigationView& nav) {
|
SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||||
add_children({&labels,
|
add_children({&button_save,
|
||||||
&field_fake_brightness,
|
|
||||||
&button_save,
|
|
||||||
&button_cancel,
|
&button_cancel,
|
||||||
&checkbox_invert_switch,
|
&checkbox_invert_switch});
|
||||||
&checkbox_brightness_switch});
|
|
||||||
|
|
||||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
|
||||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
|
||||||
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
|
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
|
||||||
|
|
||||||
button_save.on_select = [&nav, this](Button&) {
|
button_save.on_select = [&nav, this](Button&) {
|
||||||
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
|
|
||||||
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
|
|
||||||
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
|
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
|
||||||
display.set_inverted(checkbox_invert_switch.value());
|
display.set_inverted(checkbox_invert_switch.value());
|
||||||
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
|
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
|
||||||
@ -798,13 +791,6 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
|
|||||||
send_system_refresh();
|
send_system_refresh();
|
||||||
nav.pop();
|
nav.pop();
|
||||||
};
|
};
|
||||||
// only enable invert OR fake brightness
|
|
||||||
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
|
|
||||||
if (v) checkbox_brightness_switch.set_value(false);
|
|
||||||
};
|
|
||||||
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
|
|
||||||
if (v) checkbox_invert_switch.set_value(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
button_cancel.on_select = [&nav, this](Button&) {
|
button_cancel.on_select = [&nav, this](Button&) {
|
||||||
nav.pop();
|
nav.pop();
|
||||||
@ -815,6 +801,107 @@ void SetDisplayView::focus() {
|
|||||||
button_save.focus();
|
button_save.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SetTouchscreenSensitivityView ************************************/
|
||||||
|
/* sample max: 1023 sample_t AKA uint16_t
|
||||||
|
* touch_sensitivity: range: 1 to 128
|
||||||
|
* threshold = 1023 / sensitive
|
||||||
|
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||||
|
*/
|
||||||
|
SetTouchscreenThresholdView::SetTouchscreenThresholdView(NavigationView& nav) {
|
||||||
|
add_children({&labels,
|
||||||
|
&field_threshold,
|
||||||
|
&button_autodetect,
|
||||||
|
&button_reset,
|
||||||
|
&button_save,
|
||||||
|
&button_cancel,
|
||||||
|
&text_hint,
|
||||||
|
&text_wait_timer});
|
||||||
|
|
||||||
|
set_dirty();
|
||||||
|
org_threshold = portapack::touch_threshold;
|
||||||
|
field_threshold.set_value(pmem::touchscreen_threshold());
|
||||||
|
text_hint.set_style(Theme::getInstance()->error_dark);
|
||||||
|
text_hint.hidden(true);
|
||||||
|
text_wait_timer.set_style(Theme::getInstance()->error_dark);
|
||||||
|
text_wait_timer.hidden(true);
|
||||||
|
// clang-format off
|
||||||
|
button_autodetect.on_select = [this, &nav](Button&) {
|
||||||
|
nav.display_modal("NOTICE",
|
||||||
|
"Now on don't touch screen;\n"
|
||||||
|
"Use arrow keys to operate.\n"
|
||||||
|
"Follow instructions.\n"
|
||||||
|
"Press YES to continue",
|
||||||
|
YESNO, [this, &nav](bool choice) {
|
||||||
|
if (choice){
|
||||||
|
time_start_auto_detect = chTimeNow();
|
||||||
|
text_hint.hidden(false);
|
||||||
|
text_wait_timer.hidden(false);
|
||||||
|
text_wait_timer.set("ETA " + to_string_dec_uint(10) + "s");
|
||||||
|
in_auto_detect = true;
|
||||||
|
field_threshold.set_value(1);
|
||||||
|
portapack::touch_threshold = 1;
|
||||||
|
set_dirty(); } }, TRUE);
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
button_reset.on_select = [this](Button&) {
|
||||||
|
field_threshold.set_value(32);
|
||||||
|
portapack::touch_threshold = 32;
|
||||||
|
};
|
||||||
|
|
||||||
|
button_save.on_select = [&nav, this](Button&) {
|
||||||
|
pmem::set_touchscreen_threshold(field_threshold.value());
|
||||||
|
portapack::touch_threshold = field_threshold.value();
|
||||||
|
send_system_refresh();
|
||||||
|
nav.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_cancel.on_select = [&nav, this](Button&) {
|
||||||
|
portapack::touch_threshold = org_threshold;
|
||||||
|
nav.pop();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTouchscreenThresholdView::focus() {
|
||||||
|
button_autodetect.focus();
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTouchscreenThresholdView::on_frame_sync() {
|
||||||
|
if (!in_auto_detect) return;
|
||||||
|
uint32_t time_now = chTimeNow();
|
||||||
|
int32_t time_diff = time_now - time_start_auto_detect;
|
||||||
|
text_wait_timer.set("ETA " + to_string_dec_uint((10 - time_diff / 1000) <= 0 ? 0 : 10 - time_diff / 1000) + "s");
|
||||||
|
if (time_diff >= 10001 && !auto_detect_succeed_consumed) { // 10s
|
||||||
|
in_auto_detect = false;
|
||||||
|
text_wait_timer.hidden(true);
|
||||||
|
text_hint.set("OK, press save and reboot");
|
||||||
|
portapack::touch_threshold = org_threshold;
|
||||||
|
pmem::set_touchscreen_threshold(org_threshold);
|
||||||
|
set_dirty();
|
||||||
|
auto_detect_succeed_consumed = true;
|
||||||
|
button_save.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (get_touch_frame().touch) {
|
||||||
|
if (in_auto_detect) {
|
||||||
|
uint16_t sen = field_threshold.value();
|
||||||
|
sen++;
|
||||||
|
portapack::touch_threshold = sen;
|
||||||
|
field_threshold.set_value(sen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTouchscreenThresholdView::~SetTouchscreenThresholdView() {
|
||||||
|
// it seems that sometimes in the msg handler func it would enter the condi that not possible to entered,
|
||||||
|
// so added this workaround.
|
||||||
|
// TODO: find out why
|
||||||
|
in_auto_detect = false;
|
||||||
|
auto_detect_succeed_consumed = false;
|
||||||
|
time_start_auto_detect = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* SetMenuColorView ************************************/
|
/* SetMenuColorView ************************************/
|
||||||
|
|
||||||
void SetMenuColorView::paint_sample() {
|
void SetMenuColorView::paint_sample() {
|
||||||
@ -876,6 +963,7 @@ SetAutostartView::SetAutostartView(NavigationView& nav) {
|
|||||||
add_children({&labels,
|
add_children({&labels,
|
||||||
&button_save,
|
&button_save,
|
||||||
&button_cancel,
|
&button_cancel,
|
||||||
|
&button_reset,
|
||||||
&options});
|
&options});
|
||||||
|
|
||||||
button_save.on_select = [&nav, this](Button&) {
|
button_save.on_select = [&nav, this](Button&) {
|
||||||
@ -892,6 +980,12 @@ SetAutostartView::SetAutostartView(NavigationView& nav) {
|
|||||||
nav.pop();
|
nav.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
button_reset.on_select = [this](Button&) {
|
||||||
|
selected = 0;
|
||||||
|
options.set_selected_index(0);
|
||||||
|
autostart_app = "";
|
||||||
|
};
|
||||||
|
|
||||||
// options
|
// options
|
||||||
i = 0;
|
i = 0;
|
||||||
OptionsField::option_t o{"-none-", i};
|
OptionsField::option_t o{"-none-", i};
|
||||||
@ -969,7 +1063,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
|||||||
&button_cancel,
|
&button_cancel,
|
||||||
&checkbox_overridebatt});
|
&checkbox_overridebatt});
|
||||||
|
|
||||||
if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BATT_MAX17055) add_children({&button_reset, &labels2});
|
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
|
||||||
|
|
||||||
button_save.on_select = [&nav, this](Button&) {
|
button_save.on_select = [&nav, this](Button&) {
|
||||||
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
|
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
|
||||||
@ -979,7 +1073,8 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
button_reset.on_select = [&nav, this](Button&) {
|
button_reset.on_select = [&nav, this](Button&) {
|
||||||
if (battery::BatteryManagement::reset_learned())
|
auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
|
if (dev->reset_learned())
|
||||||
nav.display_modal("Reset", "Battery parameters reset");
|
nav.display_modal("Reset", "Battery parameters reset");
|
||||||
else
|
else
|
||||||
nav.display_modal("Error", "Error parameter reset");
|
nav.display_modal("Error", "Error parameter reset");
|
||||||
@ -1011,6 +1106,7 @@ void SettingsMenuView::on_populate() {
|
|||||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
|
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
|
||||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
|
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
|
||||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<TouchCalibrationView>(); }},
|
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<TouchCalibrationView>(); }},
|
||||||
|
{"TouchThreshold", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<SetTouchscreenThresholdView>(); }},
|
||||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push<SetConfigModeView>(); }},
|
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push<SetConfigModeView>(); }},
|
||||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
|
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
|
||||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
|
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
#include "bitmap.hpp"
|
#include "bitmap.hpp"
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "irq_controls.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@ -362,12 +363,8 @@ class SetUIView : public View {
|
|||||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||||
&bitmap_icon_batt_text};
|
&bitmap_icon_batt_text};
|
||||||
|
|
||||||
ImageToggle toggle_fake_brightness{
|
|
||||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
|
||||||
&bitmap_icon_brightness};
|
|
||||||
|
|
||||||
ImageToggle toggle_sd_card{
|
ImageToggle toggle_sd_card{
|
||||||
{23 * 8, 14 * 16 + 2, 16, 16},
|
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||||
&bitmap_sd_card_ok};
|
&bitmap_sd_card_ok};
|
||||||
|
|
||||||
Button button_save{
|
Button button_save{
|
||||||
@ -709,8 +706,6 @@ class SetConfigModeView : public View {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
using portapack::persistent_memory::fake_brightness_level_options;
|
|
||||||
|
|
||||||
class SetDisplayView : public View {
|
class SetDisplayView : public View {
|
||||||
public:
|
public:
|
||||||
SetDisplayView(NavigationView& nav);
|
SetDisplayView(NavigationView& nav);
|
||||||
@ -720,27 +715,8 @@ class SetDisplayView : public View {
|
|||||||
std::string title() const override { return "Display"; };
|
std::string title() const override { return "Display"; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
|
||||||
{{1 * 8, 1 * 16}, "Limits screen brightness", Theme::getInstance()->fg_light->foreground},
|
|
||||||
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
|
|
||||||
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
|
|
||||||
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
|
|
||||||
};
|
|
||||||
|
|
||||||
OptionsField field_fake_brightness{
|
|
||||||
{20 * 8, 8 * 16},
|
|
||||||
6,
|
|
||||||
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
|
|
||||||
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
|
|
||||||
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
|
|
||||||
|
|
||||||
Checkbox checkbox_brightness_switch{
|
|
||||||
{1 * 8, 5 * 16},
|
|
||||||
16,
|
|
||||||
"Enable brightness adjust"};
|
|
||||||
|
|
||||||
Checkbox checkbox_invert_switch{
|
Checkbox checkbox_invert_switch{
|
||||||
{1 * 8, 10 * 16},
|
{1 * 8, 2 * 16},
|
||||||
23,
|
23,
|
||||||
"Invert colors (For IPS)"};
|
"Invert colors (For IPS)"};
|
||||||
|
|
||||||
@ -754,6 +730,77 @@ class SetDisplayView : public View {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using portapack::persistent_memory::touchscreen_threshold;
|
||||||
|
|
||||||
|
class SetTouchscreenThresholdView : public View {
|
||||||
|
public:
|
||||||
|
SetTouchscreenThresholdView(NavigationView& nav);
|
||||||
|
~SetTouchscreenThresholdView();
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
|
|
||||||
|
std::string title() const override { return "Touch S"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool in_auto_detect = false;
|
||||||
|
uint16_t org_threshold = 0;
|
||||||
|
uint8_t auto_detect_succeed_consumed = false; // prevent screen flash but can still change text content
|
||||||
|
uint32_t time_start_auto_detect = 0;
|
||||||
|
|
||||||
|
Labels labels{
|
||||||
|
{{1 * 8, 1 * 16}, "Set touchscreen sensitivity", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{1 * 8, 2 * 16}, "Or press auto detect button", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{1 * 8, 3 * 16}, "FOLLOW INSTRUCTIONS", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{1 * 8, 4 * 16}, "REBOOT TO APPLY", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{1 * 8, 11 * 16}, "Threshold:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
};
|
||||||
|
|
||||||
|
Text text_hint{
|
||||||
|
{1 * 8, 7 * 16, screen_width - 2 * 8, 1 * 16},
|
||||||
|
"DON'T TOUCH SCREEN"};
|
||||||
|
|
||||||
|
Text text_wait_timer{
|
||||||
|
{1 * 8, 8 * 16, screen_width - 2 * 8, 1 * 16},
|
||||||
|
"ETA 00:00"};
|
||||||
|
|
||||||
|
void on_frame_sync();
|
||||||
|
|
||||||
|
/* sample max: 1023 sample_t AKA uint16_t
|
||||||
|
* touch_sensitivity: range: 1 to 128
|
||||||
|
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||||
|
*/
|
||||||
|
NumberField field_threshold{
|
||||||
|
{1 * 8 + 11 * 8 + 8, 11 * 16},
|
||||||
|
4,
|
||||||
|
{1, 1023},
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
Button button_autodetect{
|
||||||
|
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||||
|
"Auto Detect"};
|
||||||
|
Button button_reset{
|
||||||
|
{16 * 8, 13 * 16, 12 * 8, 32},
|
||||||
|
"Reset",
|
||||||
|
};
|
||||||
|
|
||||||
|
Button button_save{
|
||||||
|
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||||
|
"Save"};
|
||||||
|
|
||||||
|
Button button_cancel{
|
||||||
|
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||||
|
"Cancel",
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_frame_sync{
|
||||||
|
Message::ID::DisplayFrameSync,
|
||||||
|
[this](const Message* const) {
|
||||||
|
this->on_frame_sync();
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
|
||||||
class SetMenuColorView : public View {
|
class SetMenuColorView : public View {
|
||||||
public:
|
public:
|
||||||
SetMenuColorView(NavigationView& nav);
|
SetMenuColorView(NavigationView& nav);
|
||||||
@ -843,14 +890,19 @@ class SetAutostartView : public View {
|
|||||||
"Save"};
|
"Save"};
|
||||||
|
|
||||||
OptionsField options{
|
OptionsField options{
|
||||||
{8 * 8, 4 * 16},
|
{0 * 8, 4 * 16},
|
||||||
30,
|
screen_width / 8,
|
||||||
{}};
|
{},
|
||||||
|
true};
|
||||||
|
|
||||||
Button button_cancel{
|
Button button_cancel{
|
||||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||||
"Cancel",
|
"Cancel",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Button button_reset{
|
||||||
|
{2 * 8, 6 * 16, screen_width - 4 * 8, 32},
|
||||||
|
"Reset"};
|
||||||
};
|
};
|
||||||
|
|
||||||
class SetThemeView : public View {
|
class SetThemeView : public View {
|
||||||
@ -872,15 +924,16 @@ class SetThemeView : public View {
|
|||||||
"Save"};
|
"Save"};
|
||||||
|
|
||||||
OptionsField options{
|
OptionsField options{
|
||||||
{8 * 8, 4 * 16},
|
{0 * 8, 4 * 16},
|
||||||
30,
|
screen_width / 8,
|
||||||
{
|
{
|
||||||
{"Default - Grey", 0},
|
{"Default - Grey", 0},
|
||||||
{"Yellow", 1},
|
{"Yellow", 1},
|
||||||
{"Aqua", 2},
|
{"Aqua", 2},
|
||||||
{"Green", 3},
|
{"Green", 3},
|
||||||
{"Red", 4},
|
{"Red", 4},
|
||||||
}};
|
},
|
||||||
|
true};
|
||||||
|
|
||||||
Checkbox checkbox_menuset{
|
Checkbox checkbox_menuset{
|
||||||
{2 * 8, 6 * 16},
|
{2 * 8, 6 * 16},
|
||||||
|
@ -56,7 +56,7 @@ SIGFRXView::~SIGFRXView() {
|
|||||||
void SIGFRXView::paint(Painter& painter) {
|
void SIGFRXView::paint(Painter& painter) {
|
||||||
uint8_t i, xp;
|
uint8_t i, xp;
|
||||||
|
|
||||||
// portapack::display.drawBMP({0, 302-160}, fox_bmp);
|
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
|
||||||
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||||
for (i = 0; i < 6; i++) {
|
for (i = 0; i < 6; i++) {
|
||||||
xp = sigfrx_marks[i * 3];
|
xp = sigfrx_marks[i * 3];
|
||||||
|
@ -119,7 +119,7 @@ bool SplashViewer::on_key(const KeyEvent key) {
|
|||||||
void SplashViewer::paint(Painter& painter) {
|
void SplashViewer::paint(Painter& painter) {
|
||||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
|
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
|
||||||
|
|
||||||
if (!portapack::display.drawBMP2({0, 0}, path_)) {
|
if (!portapack::display.draw_bmp_from_sdcard_file({0, 0}, path_)) {
|
||||||
painter.draw_string({10, 160}, *Theme::getInstance()->bg_darkest, "Not a valid splash image.");
|
painter.draw_string({10, 160}, *Theme::getInstance()->bg_darkest, "Not a valid splash image.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,12 @@
|
|||||||
#include "ui_standalone_view.hpp"
|
#include "ui_standalone_view.hpp"
|
||||||
#include "irq_controls.hpp"
|
#include "irq_controls.hpp"
|
||||||
|
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
#include "i2cdev_ppmod.hpp"
|
||||||
|
|
||||||
|
#include "ui_font_fixed_5x8.hpp"
|
||||||
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
|
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
|
||||||
@ -34,6 +40,16 @@ void fill_rectangle(int x, int y, int width, int height, uint16_t color) {
|
|||||||
painter.fill_rectangle({x, y, width, height}, ui::Color(color));
|
painter.fill_rectangle({x, y, width, height}, ui::Color(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fill_rectangle_unrolled8(int x, int y, int width, int height, uint16_t color) {
|
||||||
|
ui::Painter painter;
|
||||||
|
painter.fill_rectangle_unrolled8({x, y, width, height}, ui::Color(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_bitmap(int x, int y, int width, int height, const uint8_t* pixels, uint16_t foreground, uint16_t background) {
|
||||||
|
ui::Painter painter;
|
||||||
|
painter.draw_bitmap({x, y}, {{width, height}, pixels}, ui::Color(foreground), ui::Color(background));
|
||||||
|
}
|
||||||
|
|
||||||
void* alloc(size_t size) {
|
void* alloc(size_t size) {
|
||||||
void* p = chHeapAlloc(0x0, size);
|
void* p = chHeapAlloc(0x0, size);
|
||||||
if (p == nullptr)
|
if (p == nullptr)
|
||||||
@ -45,6 +61,45 @@ uint64_t get_switches_state_ulong() {
|
|||||||
return get_switches_state().to_ulong();
|
return get_switches_state().to_ulong();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui::Coord scroll_area_y(const ui::Coord y) {
|
||||||
|
return portapack::display.scroll_area_y(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scroll_set_area(const ui::Coord top_y, const ui::Coord bottom_y) {
|
||||||
|
portapack::display.scroll_set_area(top_y, bottom_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scroll_disable() {
|
||||||
|
portapack::display.scroll_disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::Coord scroll_set_position(const ui::Coord position) {
|
||||||
|
return portapack::display.scroll_set_position(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::Coord scroll(const int32_t delta) {
|
||||||
|
return portapack::display.scroll(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
|
||||||
|
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||||
|
if (!dev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_len == 0 || data == nullptr)
|
||||||
|
return dev->i2c_write(nullptr, 0, cmd, cmd_len);
|
||||||
|
|
||||||
|
return dev->i2c_read(cmd, cmd_len, data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
StandaloneView* standaloneView = nullptr;
|
||||||
|
|
||||||
|
void set_dirty() {
|
||||||
|
if (standaloneView != nullptr)
|
||||||
|
standaloneView->set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
standalone_application_api_t api = {
|
standalone_application_api_t api = {
|
||||||
/* .malloc = */ &alloc,
|
/* .malloc = */ &alloc,
|
||||||
/* .calloc = */ &calloc,
|
/* .calloc = */ &calloc,
|
||||||
@ -54,20 +109,78 @@ standalone_application_api_t api = {
|
|||||||
/* .fill_rectangle = */ &fill_rectangle,
|
/* .fill_rectangle = */ &fill_rectangle,
|
||||||
/* .swizzled_switches = */ &swizzled_switches,
|
/* .swizzled_switches = */ &swizzled_switches,
|
||||||
/* .get_switches_state = */ &get_switches_state_ulong,
|
/* .get_switches_state = */ &get_switches_state_ulong,
|
||||||
|
/* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
|
||||||
|
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
|
||||||
|
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
|
||||||
|
/* .draw_bitmap = */ &draw_bitmap,
|
||||||
|
/* .scroll_area_y = */ &scroll_area_y,
|
||||||
|
/* .scroll_set_area = */ &scroll_set_area,
|
||||||
|
/* .scroll_disable = */ &scroll_disable,
|
||||||
|
/* .scroll_set_position = */ &scroll_set_position,
|
||||||
|
/* .scroll = */ &scroll,
|
||||||
|
/* .i2c_read = */ &i2c_read,
|
||||||
|
/* .panic = */ &chDbgPanic,
|
||||||
|
/* .set_dirty = */ &set_dirty,
|
||||||
};
|
};
|
||||||
|
|
||||||
StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image)
|
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
|
||||||
: nav_(nav), _app_image(std::move(app_image)) {
|
: nav_(nav),
|
||||||
get_application_information()->initialize(api);
|
_app_image(*app_image) {
|
||||||
add_children({&dummy});
|
if (app_image == nullptr) {
|
||||||
|
chDbgPanic("Invalid application image");
|
||||||
|
}
|
||||||
|
|
||||||
|
set_focusable(true);
|
||||||
|
|
||||||
|
standaloneView = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandaloneView::focus() {
|
void StandaloneView::focus() {
|
||||||
dummy.focus();
|
View::focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandaloneView::paint(Painter& painter) {
|
void StandaloneView::paint(Painter& painter) {
|
||||||
(void)painter;
|
(void)painter;
|
||||||
|
|
||||||
|
if (initialized &&
|
||||||
|
get_application_information()->header_version > 1) {
|
||||||
|
get_application_information()->PaintViewMirror();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StandaloneView::on_focus() {
|
||||||
|
if (get_application_information()->header_version > 1) {
|
||||||
|
get_application_information()->OnFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StandaloneView::on_key(const KeyEvent key) {
|
||||||
|
if (get_application_information()->header_version > 1) {
|
||||||
|
return get_application_information()->OnKeyEvent((uint8_t)key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StandaloneView::on_encoder(const EncoderEvent event) {
|
||||||
|
if (get_application_information()->header_version > 1) {
|
||||||
|
return get_application_information()->OnEncoder((int32_t)event);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StandaloneView::on_touch(const TouchEvent event) {
|
||||||
|
if (get_application_information()->header_version > 1) {
|
||||||
|
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
|
||||||
|
if (get_application_information()->header_version > 1) {
|
||||||
|
return get_application_information()->OnKeyboard((uint8_t)event);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandaloneView::frame_sync() {
|
void StandaloneView::frame_sync() {
|
||||||
@ -79,4 +192,14 @@ void StandaloneView::frame_sync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StandaloneView::on_after_attach() {
|
||||||
|
context().focus_manager().setMirror(this);
|
||||||
|
get_application_information()->initialize(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StandaloneView::on_before_detach() {
|
||||||
|
get_application_information()->shutdown();
|
||||||
|
context().focus_manager().clearMirror();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
@ -31,28 +31,34 @@ namespace ui {
|
|||||||
|
|
||||||
class StandaloneView : public View {
|
class StandaloneView : public View {
|
||||||
public:
|
public:
|
||||||
StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image);
|
StandaloneView(NavigationView& nav, uint8_t* app_image);
|
||||||
virtual ~StandaloneView() override { get_application_information()->shutdown(); }
|
virtual ~StandaloneView() override {
|
||||||
|
}
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return (const char*)get_application_information()->app_name; };
|
std::string title() const override { return (const char*)get_application_information()->app_name; };
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
|
void on_focus() override;
|
||||||
|
bool on_key(const KeyEvent key) override;
|
||||||
|
bool on_encoder(const EncoderEvent event) override;
|
||||||
|
bool on_touch(const TouchEvent event) override;
|
||||||
|
bool on_keyboard(const KeyboardEvent event) override;
|
||||||
|
|
||||||
|
void on_after_attach() override;
|
||||||
|
void on_before_detach() override;
|
||||||
|
|
||||||
void frame_sync();
|
void frame_sync();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
std::unique_ptr<uint8_t[]> _app_image;
|
uint8_t& _app_image;
|
||||||
standalone_application_information_t* get_application_information() const {
|
standalone_application_information_t* get_application_information() const {
|
||||||
return reinterpret_cast<standalone_application_information_t*>(_app_image.get());
|
return reinterpret_cast<standalone_application_information_t*>(&_app_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
Button dummy{
|
|
||||||
{240, 0, 0, 0},
|
|
||||||
""};
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_sample{
|
MessageHandlerRegistration message_handler_sample{
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
[this](const Message* const) {
|
[this](const Message* const) {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "audio.hpp"
|
#include "audio.hpp"
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
@ -31,6 +32,18 @@ using namespace ui;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
std::string SubGhzDRecentEntry::to_csv() {
|
||||||
|
std::string csv = ";";
|
||||||
|
csv += SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)sensorType);
|
||||||
|
csv += ";" + to_string_dec_uint(bits) + ";";
|
||||||
|
csv += to_string_hex(data, 64 / 4);
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubGhzDLogger::log_data(SubGhzDRecentEntry& data) {
|
||||||
|
log_file.write_entry(data.to_csv());
|
||||||
|
}
|
||||||
|
|
||||||
void SubGhzDRecentEntryDetailView::update_data() {
|
void SubGhzDRecentEntryDetailView::update_data() {
|
||||||
// process protocol data
|
// process protocol data
|
||||||
parseProtocol();
|
parseProtocol();
|
||||||
@ -76,16 +89,25 @@ SubGhzDView::SubGhzDView(NavigationView& nav)
|
|||||||
&field_vga,
|
&field_vga,
|
||||||
&field_frequency,
|
&field_frequency,
|
||||||
&button_clear_list,
|
&button_clear_list,
|
||||||
|
&check_log,
|
||||||
&recent_entries_view});
|
&recent_entries_view});
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
|
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
|
||||||
|
logger = std::make_unique<SubGhzDLogger>();
|
||||||
|
|
||||||
button_clear_list.on_select = [this](Button&) {
|
button_clear_list.on_select = [this](Button&) {
|
||||||
recent.clear();
|
recent.clear();
|
||||||
recent_entries_view.set_dirty();
|
recent_entries_view.set_dirty();
|
||||||
};
|
};
|
||||||
field_frequency.set_step(10000);
|
field_frequency.set_step(10000);
|
||||||
|
check_log.on_select = [this](Checkbox&, bool v) {
|
||||||
|
logging = v;
|
||||||
|
if (logger && logging) {
|
||||||
|
logger->append(logs_dir.string() + "/SUBGHZDLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
|
||||||
|
logger->write_header();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
check_log.set_value(logging);
|
||||||
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
||||||
recent_entries_view.set_parent_rect(content_rect);
|
recent_entries_view.set_parent_rect(content_rect);
|
||||||
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
|
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
|
||||||
@ -107,6 +129,9 @@ void SubGhzDView::on_tick_second() {
|
|||||||
|
|
||||||
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
|
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
|
||||||
SubGhzDRecentEntry key{data->sensorType, data->data, data->bits};
|
SubGhzDRecentEntry key{data->sensorType, data->data, data->bits};
|
||||||
|
if (logger && logging) {
|
||||||
|
logger->log_data(key);
|
||||||
|
}
|
||||||
auto matching_recent = find(recent, key.key());
|
auto matching_recent = find(recent, key.key());
|
||||||
if (matching_recent != std::end(recent)) {
|
if (matching_recent != std::end(recent)) {
|
||||||
// Found within. Move to front of list, increment counter.
|
// Found within. Move to front of list, increment counter.
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "log_file.hpp"
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
#include "../baseband/fprotos/subghztypes.hpp"
|
#include "../baseband/fprotos/subghztypes.hpp"
|
||||||
@ -68,6 +69,23 @@ struct SubGhzDRecentEntry {
|
|||||||
void reset_age() {
|
void reset_age() {
|
||||||
age = 0;
|
age = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string to_csv();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubGhzDLogger {
|
||||||
|
public:
|
||||||
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
|
return log_file.append(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_data(SubGhzDRecentEntry& data);
|
||||||
|
void write_header() {
|
||||||
|
log_file.write_entry(";Type; Bits; Data;");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LogFile log_file{};
|
||||||
};
|
};
|
||||||
using SubGhzDRecentEntries = RecentEntries<SubGhzDRecentEntry>;
|
using SubGhzDRecentEntries = RecentEntries<SubGhzDRecentEntry>;
|
||||||
using SubGhzDRecentEntriesView = RecentEntriesView<SubGhzDRecentEntries>;
|
using SubGhzDRecentEntriesView = RecentEntriesView<SubGhzDRecentEntries>;
|
||||||
@ -93,10 +111,13 @@ class SubGhzDView : public View {
|
|||||||
1'750'000 /* bandwidth */,
|
1'750'000 /* bandwidth */,
|
||||||
4'000'000 /* sampling rate */,
|
4'000'000 /* sampling rate */,
|
||||||
ReceiverModel::Mode::AMAudio};
|
ReceiverModel::Mode::AMAudio};
|
||||||
|
bool logging = false;
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_subghzd",
|
"rx_subghzd",
|
||||||
app_settings::Mode::RX,
|
app_settings::Mode::RX,
|
||||||
{}};
|
{
|
||||||
|
{"log"sv, &logging},
|
||||||
|
}};
|
||||||
|
|
||||||
SubGhzDRecentEntries recent{};
|
SubGhzDRecentEntries recent{};
|
||||||
|
|
||||||
@ -118,8 +139,16 @@ class SubGhzDView : public View {
|
|||||||
{0, 16, 7 * 8, 32},
|
{0, 16, 7 * 8, 32},
|
||||||
"Clear"};
|
"Clear"};
|
||||||
|
|
||||||
|
Checkbox check_log{
|
||||||
|
{10 * 8, 18},
|
||||||
|
3,
|
||||||
|
"Log",
|
||||||
|
true};
|
||||||
|
|
||||||
static constexpr auto header_height = 3 * 16;
|
static constexpr auto header_height = 3 * 16;
|
||||||
|
|
||||||
|
std::unique_ptr<SubGhzDLogger> logger{};
|
||||||
|
|
||||||
const RecentEntriesColumns columns{{
|
const RecentEntriesColumns columns{{
|
||||||
{"Type", 19},
|
{"Type", 19},
|
||||||
{"Bits", 4},
|
{"Bits", 4},
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2018 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui_tone_search.hpp"
|
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "string_format.hpp"
|
|
||||||
|
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
void ToneSearchView::focus() {
|
|
||||||
// field_frequency_min.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ToneSearchView::~ToneSearchView() {
|
|
||||||
receiver_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
ToneSearchView::ToneSearchView(
|
|
||||||
NavigationView& nav)
|
|
||||||
: nav_(nav) {
|
|
||||||
// baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
|
||||||
|
|
||||||
add_children({
|
|
||||||
&labels,
|
|
||||||
&field_lna,
|
|
||||||
&field_vga,
|
|
||||||
&field_rf_amp,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2018 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "receiver_model.hpp"
|
|
||||||
|
|
||||||
#include "ui_receiver.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class ToneSearchView : public View {
|
|
||||||
public:
|
|
||||||
ToneSearchView(NavigationView& nav);
|
|
||||||
~ToneSearchView();
|
|
||||||
|
|
||||||
void focus() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Tone search"; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
NavigationView& nav_;
|
|
||||||
|
|
||||||
Labels labels{
|
|
||||||
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
|
|
||||||
|
|
||||||
LNAGainField field_lna{
|
|
||||||
{4 * 8, 0 * 16}};
|
|
||||||
|
|
||||||
VGAGainField field_vga{
|
|
||||||
{11 * 8, 0 * 16}};
|
|
||||||
|
|
||||||
RFAmpField field_rf_amp{
|
|
||||||
{18 * 8, 0 * 16}};
|
|
||||||
|
|
||||||
/*
|
|
||||||
MessageHandlerRegistration message_handler_frame_sync {
|
|
||||||
Message::ID::DisplayFrameSync,
|
|
||||||
[this](const Message* const) {
|
|
||||||
if( this->fifo ) {
|
|
||||||
ChannelSpectrum channel_spectrum;
|
|
||||||
while( fifo->out(channel_spectrum) ) {
|
|
||||||
this->on_channel_spectrum(channel_spectrum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->do_timers();
|
|
||||||
}
|
|
||||||
};*/
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
@ -25,6 +25,7 @@
|
|||||||
#include "audio.hpp"
|
#include "audio.hpp"
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "../baseband/fprotos/fprotogeneral.hpp"
|
#include "../baseband/fprotos/fprotogeneral.hpp"
|
||||||
|
|
||||||
@ -35,6 +36,21 @@ namespace pmem = portapack::persistent_memory;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
std::string WeatherRecentEntry::to_csv() {
|
||||||
|
std::string csv = ";";
|
||||||
|
csv += WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)sensorType);
|
||||||
|
csv += ";" + to_string_dec_uint(id) + ";";
|
||||||
|
csv += to_string_decimal(temp, 2) + ";";
|
||||||
|
csv += to_string_dec_uint(humidity) + ";";
|
||||||
|
csv += to_string_dec_uint(channel) + ";";
|
||||||
|
csv += to_string_dec_uint(battery_low);
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherLogger::log_data(WeatherRecentEntry& data) {
|
||||||
|
log_file.write_entry(data.to_csv());
|
||||||
|
}
|
||||||
|
|
||||||
void WeatherRecentEntryDetailView::update_data() {
|
void WeatherRecentEntryDetailView::update_data() {
|
||||||
// set text elements
|
// set text elements
|
||||||
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
|
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
|
||||||
@ -98,8 +114,11 @@ WeatherView::WeatherView(NavigationView& nav)
|
|||||||
&field_frequency,
|
&field_frequency,
|
||||||
&options_temperature,
|
&options_temperature,
|
||||||
&button_clear_list,
|
&button_clear_list,
|
||||||
|
&check_log,
|
||||||
&recent_entries_view});
|
&recent_entries_view});
|
||||||
|
|
||||||
|
logger = std::make_unique<WeatherLogger>();
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_weather);
|
baseband::run_image(portapack::spi_flash::image_tag_weather);
|
||||||
|
|
||||||
button_clear_list.on_select = [this](Button&) {
|
button_clear_list.on_select = [this](Button&) {
|
||||||
@ -114,6 +133,15 @@ WeatherView::WeatherView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
options_temperature.set_selected_index(weather_units_fahr, false);
|
options_temperature.set_selected_index(weather_units_fahr, false);
|
||||||
|
|
||||||
|
check_log.on_select = [this](Checkbox&, bool v) {
|
||||||
|
logging = v;
|
||||||
|
if (logger && logging) {
|
||||||
|
logger->append(logs_dir.string() + "/WEATHERLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
|
||||||
|
logger->write_header();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
check_log.set_value(logging);
|
||||||
|
|
||||||
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
||||||
recent_entries_view.set_parent_rect(content_rect);
|
recent_entries_view.set_parent_rect(content_rect);
|
||||||
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
|
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
|
||||||
@ -140,6 +168,9 @@ void WeatherView::on_tick_second() {
|
|||||||
|
|
||||||
void WeatherView::on_data(const WeatherDataMessage* data) {
|
void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||||
WeatherRecentEntry key = process_data(data);
|
WeatherRecentEntry key = process_data(data);
|
||||||
|
if (logger && logging) {
|
||||||
|
logger->log_data(key);
|
||||||
|
}
|
||||||
// WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
|
// WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
|
||||||
auto matching_recent = find(recent, key.key());
|
auto matching_recent = find(recent, key.key());
|
||||||
if (matching_recent != std::end(recent)) {
|
if (matching_recent != std::end(recent)) {
|
||||||
@ -213,6 +244,11 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
|||||||
return "EmosE601x";
|
return "EmosE601x";
|
||||||
case FPW_SolightTE44:
|
case FPW_SolightTE44:
|
||||||
return "SolightTE44";
|
return "SolightTE44";
|
||||||
|
case FPW_Bresser3CH:
|
||||||
|
case FPW_Bresser3CH_V1:
|
||||||
|
return "Bresser3CH";
|
||||||
|
case FPW_Vauno_EN8822:
|
||||||
|
return "Vauno EN8822";
|
||||||
case FPW_Invalid:
|
case FPW_Invalid:
|
||||||
default:
|
default:
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
@ -532,6 +568,42 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
|
|||||||
i16 |= 0xf000;
|
i16 |= 0xf000;
|
||||||
}
|
}
|
||||||
ret.temp = (float)i16 / 10.0;
|
ret.temp = (float)i16 / 10.0;
|
||||||
|
break;
|
||||||
|
case FPW_Bresser3CH:
|
||||||
|
ret.id = (data->decode_data >> 28) & 0xff;
|
||||||
|
ret.channel = ((data->decode_data >> 27) & 0x01) | (((data->decode_data >> 26) & 0x01) << 1);
|
||||||
|
// ret.btn = ((data->decode_data >> 25) & 0x1);
|
||||||
|
ret.battery_low = ((data->decode_data >> 24) & 0x1);
|
||||||
|
i16 = (data->decode_data >> 12) & 0x0fff;
|
||||||
|
/* Handle signed data */
|
||||||
|
if (i16 & 0x0800) {
|
||||||
|
i16 |= 0xf000;
|
||||||
|
}
|
||||||
|
ret.temp = (float)i16 / 10.0;
|
||||||
|
ret.humidity = data->decode_data & 0xff;
|
||||||
|
break;
|
||||||
|
case FPW_Bresser3CH_V1:
|
||||||
|
ret.id = (data->decode_data >> 32) & 0xff;
|
||||||
|
ret.battery_low = ((data->decode_data >> 31) & 0x1);
|
||||||
|
// ret.btn = (data->decode_data >> 30) & 0x1;
|
||||||
|
ret.channel = (data->decode_data >> 28) & 0x3;
|
||||||
|
ret.temp = (data->decode_data >> 16) & 0xfff;
|
||||||
|
ret.temp = FProtoGeneral::locale_fahrenheit_to_celsius((float)(ret.temp - 900) / 10.0);
|
||||||
|
ret.humidity = (data->decode_data >> 8) & 0xff;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FPW_Vauno_EN8822:
|
||||||
|
ret.id = (data->decode_data >> 34) & 0xff;
|
||||||
|
ret.battery_low = (data->decode_data >> 33) & 0x01;
|
||||||
|
ret.channel = ((data->decode_data >> 30) & 0x03);
|
||||||
|
i16 = (data->decode_data >> 18) & 0x0fff;
|
||||||
|
/* Handle signed data */
|
||||||
|
if (i16 & 0x0800) {
|
||||||
|
i16 |= 0xf000;
|
||||||
|
}
|
||||||
|
ret.temp = (float)i16 / 10.0;
|
||||||
|
ret.humidity = (data->decode_data >> 11) & 0x7f;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case FPW_Invalid:
|
case FPW_Invalid:
|
||||||
default:
|
default:
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "log_file.hpp"
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
#include "../baseband/fprotos/weathertypes.hpp"
|
#include "../baseband/fprotos/weathertypes.hpp"
|
||||||
@ -78,7 +79,25 @@ struct WeatherRecentEntry {
|
|||||||
void reset_age() {
|
void reset_age() {
|
||||||
age = 0;
|
age = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string to_csv();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WeatherLogger {
|
||||||
|
public:
|
||||||
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
|
return log_file.append(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_data(WeatherRecentEntry& data);
|
||||||
|
void write_header() {
|
||||||
|
log_file.write_entry(";Type; id; Temp; Hum; CH; Batt");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LogFile log_file{};
|
||||||
|
};
|
||||||
|
|
||||||
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
|
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
|
||||||
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
|
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
|
||||||
|
|
||||||
@ -103,11 +122,13 @@ class WeatherView : public View {
|
|||||||
1'750'000 /* bandwidth */,
|
1'750'000 /* bandwidth */,
|
||||||
2'000'000 /* sampling rate */,
|
2'000'000 /* sampling rate */,
|
||||||
ReceiverModel::Mode::AMAudio};
|
ReceiverModel::Mode::AMAudio};
|
||||||
|
bool logging = false;
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_weather",
|
"rx_weather",
|
||||||
app_settings::Mode::RX,
|
app_settings::Mode::RX,
|
||||||
{
|
{
|
||||||
{"units_fahr"sv, &weather_units_fahr},
|
{"units_fahr"sv, &weather_units_fahr},
|
||||||
|
{"log"sv, &logging},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
WeatherRecentEntries recent{};
|
WeatherRecentEntries recent{};
|
||||||
@ -140,8 +161,16 @@ class WeatherView : public View {
|
|||||||
{0, 16, 7 * 8, 32},
|
{0, 16, 7 * 8, 32},
|
||||||
"Clear"};
|
"Clear"};
|
||||||
|
|
||||||
|
Checkbox check_log{
|
||||||
|
{10 * 8, 18},
|
||||||
|
3,
|
||||||
|
"Log",
|
||||||
|
true};
|
||||||
|
|
||||||
static constexpr auto header_height = 3 * 16;
|
static constexpr auto header_height = 3 * 16;
|
||||||
|
|
||||||
|
std::unique_ptr<WeatherLogger> logger{};
|
||||||
|
|
||||||
const RecentEntriesColumns columns{{
|
const RecentEntriesColumns columns{{
|
||||||
{"Type", 10},
|
{"Type", 10},
|
||||||
{"Temp", 5},
|
{"Temp", 5},
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Jared Boone, ShareBrained Technology, Inc.
|
|
||||||
* Copyright (C) 2017 Furrtek
|
|
||||||
*
|
|
||||||
* This file is part of PortaPack.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "emu_cc1101.hpp"
|
|
||||||
|
|
||||||
namespace cc1101 {
|
|
||||||
|
|
||||||
void CC1101Emu::whitening_init() {
|
|
||||||
whitening_pn = 0x1FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See TI app note DN509
|
|
||||||
uint8_t CC1101Emu::whiten_byte(uint8_t byte) {
|
|
||||||
uint_fast8_t new_bit;
|
|
||||||
|
|
||||||
byte ^= (whitening_pn & 0xFF);
|
|
||||||
|
|
||||||
for (size_t step = 0; step < 8; step++) {
|
|
||||||
new_bit = (whitening_pn & 1) ^ ((whitening_pn >> 5) & 1);
|
|
||||||
whitening_pn >>= 1;
|
|
||||||
whitening_pn |= (new_bit << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace cc1101 */
|
|
@ -20,45 +20,26 @@
|
|||||||
* Boston, MA 02110-1301, USA.
|
* Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "acars_app.hpp"
|
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "file_path.hpp"
|
#include "file_path.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
|
||||||
|
#include "acars_app.hpp"
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace acars;
|
|
||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequency) {
|
namespace ui::external_app::acars_rx {
|
||||||
(void)frequency;
|
|
||||||
std::string entry{}; //= "Raw: F:" + to_string_dec_uint(frequency) + "Hz ";
|
|
||||||
entry.reserve(256);
|
|
||||||
|
|
||||||
// Raw hex dump of all the bytes
|
void ACARSLogger::log_str(std::string msg) {
|
||||||
// for (size_t c = 0; c < packet.length(); c += 32)
|
log_file.write_entry(msg);
|
||||||
// entry += to_string_hex(packet.read(c, 32), 8) + " ";
|
|
||||||
|
|
||||||
for (size_t c = 0; c < 256; c += 32)
|
|
||||||
entry += to_string_bin(packet.read(c, 32), 32);
|
|
||||||
|
|
||||||
log_file.write_entry(packet.received_at(), entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*void ACARSLogger::log_decoded(
|
|
||||||
const acars::Packet& packet,
|
|
||||||
const std::string text) {
|
|
||||||
|
|
||||||
log_file.write_entry(packet.timestamp(), text);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
ACARSAppView::ACARSAppView(NavigationView& nav)
|
ACARSAppView::ACARSAppView(NavigationView& nav)
|
||||||
: nav_{nav} {
|
: nav_{nav} {
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_acars);
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||||
|
|
||||||
add_children({&rssi,
|
add_children({&rssi,
|
||||||
&channel,
|
&channel,
|
||||||
@ -66,6 +47,7 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
|
|||||||
&field_lna,
|
&field_lna,
|
||||||
&field_vga,
|
&field_vga,
|
||||||
&field_frequency,
|
&field_frequency,
|
||||||
|
&field_volume,
|
||||||
&check_log,
|
&check_log,
|
||||||
&console});
|
&console});
|
||||||
|
|
||||||
@ -79,6 +61,9 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
|
|||||||
logger = std::make_unique<ACARSLogger>();
|
logger = std::make_unique<ACARSLogger>();
|
||||||
if (logger)
|
if (logger)
|
||||||
logger->append(logs_dir / u"ACARS.TXT");
|
logger->append(logs_dir / u"ACARS.TXT");
|
||||||
|
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
ACARSAppView::~ACARSAppView() {
|
ACARSAppView::~ACARSAppView() {
|
||||||
@ -90,29 +75,31 @@ void ACARSAppView::focus() {
|
|||||||
field_frequency.focus();
|
field_frequency.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACARSAppView::on_packet(const acars::Packet& packet) {
|
void ACARSAppView::on_packet(const ACARSPacketMessage* packet) {
|
||||||
std::string console_info;
|
std::string console_info;
|
||||||
|
|
||||||
/*if (!packet.is_valid()) {
|
if (packet->state == 255) {
|
||||||
console_info = to_string_datetime(packet.received_at(), HMS);
|
// got a packet, parse it, and display
|
||||||
console_info += " INVALID";
|
rtc::RTC datetime;
|
||||||
|
rtc_time::now(datetime);
|
||||||
console.writeln(console_info);
|
// todo parity error recovery
|
||||||
} else {
|
console_info = to_string_datetime(datetime, HMS);
|
||||||
console_info = to_string_datetime(packet.received_at(), HMS);
|
console_info += ": ";
|
||||||
console_info += ":" + to_string_bin(packet.read(0, 32), 32);
|
console_info += packet->message;
|
||||||
//console_info += " REG:" + packet.registration_number();
|
console.writeln(console_info);
|
||||||
|
// Log raw data whatever it contains
|
||||||
console.writeln(console_info);
|
if (logger && logging)
|
||||||
}*/
|
logger->log_str(console_info);
|
||||||
|
} else {
|
||||||
packet_counter++;
|
// debug message arrived
|
||||||
if (packet_counter % 10 == 0)
|
console_info = "State: ";
|
||||||
console.writeln("Block #" + to_string_dec_uint(packet_counter));
|
console_info += to_string_dec_int(packet->state);
|
||||||
|
console_info += " lastbyte: ";
|
||||||
// Log raw data whatever it contains
|
console_info += to_string_dec_uint(packet->message[0]);
|
||||||
if (logger && logging)
|
console.writeln(console_info);
|
||||||
logger->log_raw_data(packet, receiver_model.target_frequency());
|
if (logger && logging)
|
||||||
|
logger->log_str(console_info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::acars_rx
|
@ -31,23 +31,19 @@
|
|||||||
#include "ui_rssi.hpp"
|
#include "ui_rssi.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
|
|
||||||
#include "acars_packet.hpp"
|
namespace ui::external_app::acars_rx {
|
||||||
|
|
||||||
class ACARSLogger {
|
class ACARSLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
void log_str(std::string msg);
|
||||||
void log_raw_data(const acars::Packet& packet, const uint32_t frequency);
|
|
||||||
// void log_decoded(const acars::Packet& packet, const std::string text);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LogFile log_file{};
|
LogFile log_file{};
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class ACARSAppView : public View {
|
class ACARSAppView : public View {
|
||||||
public:
|
public:
|
||||||
ACARSAppView(NavigationView& nav);
|
ACARSAppView(NavigationView& nav);
|
||||||
@ -55,12 +51,12 @@ class ACARSAppView : public View {
|
|||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
std::string title() const override { return "ACARS (WIP)"; };
|
std::string title() const override { return "ACARS"; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{
|
RxRadioState radio_state_{
|
||||||
131550000 /* frequency */,
|
131825000 /* frequency */,
|
||||||
1750000 /* bandwidth */,
|
1750000 /* bandwidth */,
|
||||||
2457600 /* sampling rate */
|
2457600 /* sampling rate */
|
||||||
};
|
};
|
||||||
@ -85,7 +81,7 @@ class ACARSAppView : public View {
|
|||||||
{0 * 8, 0 * 8},
|
{0 * 8, 0 * 8},
|
||||||
nav_};
|
nav_};
|
||||||
Checkbox check_log{
|
Checkbox check_log{
|
||||||
{22 * 8, 21},
|
{16 * 8, 1 * 16},
|
||||||
3,
|
3,
|
||||||
"LOG",
|
"LOG",
|
||||||
true};
|
true};
|
||||||
@ -93,19 +89,21 @@ class ACARSAppView : public View {
|
|||||||
Console console{
|
Console console{
|
||||||
{0, 3 * 16, 240, 256}};
|
{0, 3 * 16, 240, 256}};
|
||||||
|
|
||||||
|
AudioVolumeField field_volume{
|
||||||
|
{28 * 8, 1 * 16}};
|
||||||
|
|
||||||
std::unique_ptr<ACARSLogger> logger{};
|
std::unique_ptr<ACARSLogger> logger{};
|
||||||
|
|
||||||
void on_packet(const acars::Packet& packet);
|
void on_packet(const ACARSPacketMessage* packet);
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
Message::ID::ACARSPacket,
|
Message::ID::ACARSPacket,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
const auto message = static_cast<const ACARSPacketMessage*>(p);
|
const auto message = static_cast<const ACARSPacketMessage*>(p);
|
||||||
const acars::Packet packet{message->packet};
|
this->on_packet(message);
|
||||||
this->on_packet(packet);
|
|
||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::acars_rx
|
||||||
|
|
||||||
#endif /*__ACARS_APP_H__*/
|
#endif /*__ACARS_APP_H__*/
|
83
firmware/application/external/acars_rx/main.cpp
vendored
Normal file
83
firmware/application/external/acars_rx/main.cpp
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Bernd Herzog
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "acars_app.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::acars_rx {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<ACARSAppView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::acars_rx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_acars_rx.application_information"), used)) application_information_t _application_information_acars_rx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::acars_rx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "ACARS",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x80,
|
||||||
|
0x01,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
0xF8,
|
||||||
|
0x1F,
|
||||||
|
0xFE,
|
||||||
|
0x7F,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xC0,
|
||||||
|
0x03,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0xF8,
|
||||||
|
0x1F,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::orange().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_adsbtx.application_information"), used
|
|||||||
},
|
},
|
||||||
/*.icon_color = */ ui::Color::green().v,
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_adsbtx */ {'P', 'A', 'D', 'T'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::RX,
|
/*.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_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::RX,
|
/*.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_tag = portapack::spi_flash::image_tag_am_tv */ {'P', 'A', 'M', 'T'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::cyan().v,
|
||||||
/*.menu_location = */ app_location_t::DEBUG,
|
/*.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_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::audio_test {
|
||||||
|
|
||||||
AudioTestView::AudioTestView(NavigationView& nav)
|
AudioTestView::AudioTestView(NavigationView& nav)
|
||||||
: nav_{nav} {
|
: nav_{nav} {
|
||||||
@ -103,4 +103,4 @@ void AudioTestView::update_audio_beep() {
|
|||||||
baseband::request_beep_stop();
|
baseband::request_beep_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui::external_app::audio_test */
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::audio_test {
|
||||||
|
|
||||||
class AudioTestView : public View {
|
class AudioTestView : public View {
|
||||||
public:
|
public:
|
||||||
@ -96,6 +96,6 @@ class AudioTestView : public View {
|
|||||||
Theme::getInstance()->bg_dark->background};
|
Theme::getInstance()->bg_dark->background};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui::external_app::audio_test */
|
||||||
|
|
||||||
#endif /*__UI_AUDIO_TEST_H__*/
|
#endif /*__UI_AUDIO_TEST_H__*/
|
||||||
|
@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_blespam.application_information"), use
|
|||||||
},
|
},
|
||||||
/*.icon_color = */ ui::Color::yellow().v,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::UTILITIES,
|
/*.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_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
310
firmware/application/external/cvs_spam/cvs_spam.cpp
vendored
Normal file
310
firmware/application/external/cvs_spam/cvs_spam.cpp
vendored
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||||
|
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||||
|
// If you can read this, you're a nerd. :P
|
||||||
|
// Come join us at https://discord.gg/thepiratesreborn
|
||||||
|
|
||||||
|
#include "cvs_spam.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
#include "metadata_file.hpp"
|
||||||
|
#include "oversample.hpp"
|
||||||
|
#include "io_convert.hpp"
|
||||||
|
#include "lfsr_random.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace ui::external_app::cvs_spam {
|
||||||
|
|
||||||
|
void CVSSpamView::file_error(const std::filesystem::path& path, const std::string& error_details) {
|
||||||
|
nav_.display_modal("Error",
|
||||||
|
"File error:\n" + path.string() + "\n" + error_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::refresh_list() {
|
||||||
|
file_list.clear();
|
||||||
|
current_page = page;
|
||||||
|
uint32_t count = 0;
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(cvsfiles_dir, u"*")) {
|
||||||
|
if (std::filesystem::is_regular_file(entry.status())) {
|
||||||
|
auto filename = entry.path().filename().string();
|
||||||
|
auto extension = entry.path().extension().string();
|
||||||
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (extension == ".c16" || extension == ".cu8") {
|
||||||
|
if (count >= (page - 1) * 50 && count < page * 50) {
|
||||||
|
file_list.push_back(entry.path());
|
||||||
|
if (file_list.size() == 50) {
|
||||||
|
page++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_list.empty()) {
|
||||||
|
if (page == 1) {
|
||||||
|
menu_view.hidden(true);
|
||||||
|
text_empty.hidden(false);
|
||||||
|
} else {
|
||||||
|
page = 1;
|
||||||
|
refresh_list();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu_view.hidden(false);
|
||||||
|
text_empty.hidden(true);
|
||||||
|
menu_view.clear();
|
||||||
|
for (const auto& file : file_list) {
|
||||||
|
menu_view.add_item({file.filename().string(),
|
||||||
|
ui::Theme::getInstance()->fg_green->foreground,
|
||||||
|
nullptr,
|
||||||
|
[this](KeyEvent key) {
|
||||||
|
if (key == KeyEvent::Select) {
|
||||||
|
start_tx(menu_view.highlighted_index());
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
page_info.set("Page " + std::to_string(current_page));
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_list.size() < 50) {
|
||||||
|
page = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::start_tx(const uint32_t id) {
|
||||||
|
if (is_active()) {
|
||||||
|
stop_tx();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t sample_rate = 250000;
|
||||||
|
|
||||||
|
current_file = cvsfiles_dir / file_list[id].filename();
|
||||||
|
|
||||||
|
File capture_file;
|
||||||
|
auto open_error = capture_file.open(current_file);
|
||||||
|
if (open_error) {
|
||||||
|
file_error(current_file,
|
||||||
|
"Cannot open file.\n"
|
||||||
|
"Initial file check failed.\n"
|
||||||
|
"Path: " +
|
||||||
|
cvsfiles_dir.string() +
|
||||||
|
"\n"
|
||||||
|
"Error: " +
|
||||||
|
std::to_string(static_cast<uint32_t>(open_error)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto metadata_path = get_metadata_path(current_file);
|
||||||
|
auto metadata = read_metadata_file(metadata_path);
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file_size = capture_file.size();
|
||||||
|
capture_file.close();
|
||||||
|
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
ready_signal = false;
|
||||||
|
|
||||||
|
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
|
||||||
|
|
||||||
|
auto reader = std::make_unique<FileConvertReader>();
|
||||||
|
if (auto error = reader->open(current_file)) {
|
||||||
|
file_error(current_file,
|
||||||
|
"Cannot read C16 data.\n"
|
||||||
|
"Check file format/perms.\n"
|
||||||
|
"Rate: " +
|
||||||
|
to_string_dec_uint(metadata->sample_rate) +
|
||||||
|
"\n"
|
||||||
|
"Size: " +
|
||||||
|
to_string_dec_uint(file_size) +
|
||||||
|
"\n"
|
||||||
|
"Error: " +
|
||||||
|
std::to_string(static_cast<uint32_t>(error)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar.set_value(0);
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||||
|
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
|
||||||
|
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||||
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
chThdSleepMilliseconds(100);
|
||||||
|
|
||||||
|
replay_thread = std::make_unique<ReplayThread>(
|
||||||
|
std::move(reader),
|
||||||
|
read_size,
|
||||||
|
buffer_count,
|
||||||
|
&ready_signal,
|
||||||
|
[](uint32_t return_code) {
|
||||||
|
ReplayThreadDoneMessage message{return_code};
|
||||||
|
EventDispatcher::send_message(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::start_random_tx() {
|
||||||
|
if (is_active()) {
|
||||||
|
stop_tx();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_list.empty()) {
|
||||||
|
nav_.display_modal("Error", "No files found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfsr_v = lfsr_iterate(lfsr_v);
|
||||||
|
size_t random_index = lfsr_v % file_list.size();
|
||||||
|
|
||||||
|
const uint32_t sample_rate = 250000;
|
||||||
|
current_file = cvsfiles_dir / file_list[random_index].filename();
|
||||||
|
|
||||||
|
File capture_file;
|
||||||
|
auto open_error = capture_file.open(current_file);
|
||||||
|
if (open_error) {
|
||||||
|
file_error(current_file, "Cannot open file.\nInitial check failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto metadata_path = get_metadata_path(current_file);
|
||||||
|
auto metadata = read_metadata_file(metadata_path);
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_file.close();
|
||||||
|
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
ready_signal = false;
|
||||||
|
|
||||||
|
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
|
||||||
|
|
||||||
|
auto reader = std::make_unique<FileConvertReader>();
|
||||||
|
if (auto error = reader->open(current_file)) {
|
||||||
|
file_error(current_file, "Cannot read file data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar.set_value(0);
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||||
|
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
|
||||||
|
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||||
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
chThdSleepMilliseconds(100);
|
||||||
|
|
||||||
|
replay_thread = std::make_unique<ReplayThread>(
|
||||||
|
std::move(reader),
|
||||||
|
read_size,
|
||||||
|
buffer_count,
|
||||||
|
&ready_signal,
|
||||||
|
[](uint32_t return_code) {
|
||||||
|
ReplayThreadDoneMessage message{return_code};
|
||||||
|
EventDispatcher::send_message(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::on_tx_progress(const uint32_t progress) {
|
||||||
|
if (is_active()) {
|
||||||
|
progressbar.set_value(progress % 100);
|
||||||
|
progressbar.set_style(Theme::getInstance()->fg_red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::focus() {
|
||||||
|
menu_view.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CVSSpamView::is_active() const {
|
||||||
|
return (bool)replay_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVSSpamView::stop_tx() {
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
ready_signal = false;
|
||||||
|
thread_sync_complete = false;
|
||||||
|
chaos_mode = false;
|
||||||
|
progressbar.set_value(0);
|
||||||
|
chThdSleepMilliseconds(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
CVSSpamView::CVSSpamView(NavigationView& nav)
|
||||||
|
: nav_{nav},
|
||||||
|
current_file{} {
|
||||||
|
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||||
|
|
||||||
|
add_children({&menu_view,
|
||||||
|
&text_empty,
|
||||||
|
&button_prev_page,
|
||||||
|
&button_next_page,
|
||||||
|
&page_info,
|
||||||
|
&button_send,
|
||||||
|
&button_chaos,
|
||||||
|
&button_stop,
|
||||||
|
&progressbar});
|
||||||
|
|
||||||
|
button_send.set_style(Theme::getInstance()->fg_magenta);
|
||||||
|
button_chaos.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
button_stop.set_style(Theme::getInstance()->fg_red);
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(1536000);
|
||||||
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
|
transmitter_model.set_rf_amp(true);
|
||||||
|
transmitter_model.set_tx_gain(47);
|
||||||
|
transmitter_model.set_channel_bandwidth(1750000);
|
||||||
|
|
||||||
|
refresh_list();
|
||||||
|
|
||||||
|
button_prev_page.on_select = [this](Button&) {
|
||||||
|
if (is_active()) return;
|
||||||
|
if (current_page == 1) return;
|
||||||
|
if (current_page == 2) page = 1;
|
||||||
|
page = current_page - 1;
|
||||||
|
refresh_list();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_next_page.on_select = [this](Button&) {
|
||||||
|
if (is_active()) return;
|
||||||
|
refresh_list();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_send.on_select = [this](Button&) {
|
||||||
|
if (!file_list.empty()) {
|
||||||
|
start_tx(menu_view.highlighted_index());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button_chaos.on_select = [this](Button&) {
|
||||||
|
if (is_active()) {
|
||||||
|
chaos_mode = false;
|
||||||
|
stop_tx();
|
||||||
|
} else {
|
||||||
|
chaos_mode = true;
|
||||||
|
start_random_tx();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button_stop.on_select = [this](Button&) {
|
||||||
|
if (is_active()) {
|
||||||
|
stop_tx();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
CVSSpamView::~CVSSpamView() {
|
||||||
|
stop_tx();
|
||||||
|
baseband::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::cvs_spam
|
136
firmware/application/external/cvs_spam/cvs_spam.hpp
vendored
Normal file
136
firmware/application/external/cvs_spam/cvs_spam.hpp
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||||
|
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||||
|
// If you can read this, you're a nerd. :P
|
||||||
|
// Come join us at https://discord.gg/thepiratesreborn
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_transmitter.hpp"
|
||||||
|
#include "replay_thread.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "ui_language.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace ui::external_app::cvs_spam {
|
||||||
|
|
||||||
|
class CVSSpamView : public View {
|
||||||
|
public:
|
||||||
|
explicit CVSSpamView(NavigationView& nav);
|
||||||
|
~CVSSpamView();
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
|
std::string title() const override { return "CVS Spam"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr size_t read_size = 0x4000;
|
||||||
|
static constexpr size_t buffer_count = 3;
|
||||||
|
|
||||||
|
lfsr_word_t lfsr_v = 1;
|
||||||
|
bool chaos_mode{false};
|
||||||
|
|
||||||
|
NavigationView& nav_;
|
||||||
|
std::unique_ptr<ReplayThread> replay_thread{};
|
||||||
|
bool ready_signal{false};
|
||||||
|
bool thread_sync_complete{false};
|
||||||
|
std::filesystem::path current_file;
|
||||||
|
|
||||||
|
bool is_active() const;
|
||||||
|
void stop_tx();
|
||||||
|
void file_error(const std::filesystem::path& path, const std::string& error_details);
|
||||||
|
void refresh_list();
|
||||||
|
void start_tx(const uint32_t id);
|
||||||
|
void start_random_tx();
|
||||||
|
void on_tx_progress(const uint32_t progress);
|
||||||
|
|
||||||
|
uint32_t page = 1;
|
||||||
|
uint32_t current_page = 1;
|
||||||
|
std::vector<std::filesystem::path> file_list{};
|
||||||
|
|
||||||
|
MenuView menu_view{
|
||||||
|
{0, 0, 240, 180},
|
||||||
|
true};
|
||||||
|
|
||||||
|
Text text_empty{
|
||||||
|
{7 * 8, 12 * 8, 16 * 8, 16},
|
||||||
|
"Empty directory!"};
|
||||||
|
|
||||||
|
Button button_prev_page{
|
||||||
|
{0, 180, 50, 32},
|
||||||
|
"<"};
|
||||||
|
|
||||||
|
Button button_next_page{
|
||||||
|
{190, 180, 50, 32},
|
||||||
|
">"};
|
||||||
|
|
||||||
|
Text page_info{
|
||||||
|
{95, 188, 50, 16}};
|
||||||
|
|
||||||
|
Button button_send{
|
||||||
|
{0, 212, 75, 36},
|
||||||
|
"CALL"};
|
||||||
|
|
||||||
|
Button button_chaos{
|
||||||
|
{82, 212, 75, 36},
|
||||||
|
"CHAOS"};
|
||||||
|
|
||||||
|
Button button_stop{
|
||||||
|
{165, 212, 75, 36},
|
||||||
|
LanguageHelper::currentMessages[LANG_STOP]};
|
||||||
|
|
||||||
|
ProgressBar progressbar{
|
||||||
|
{0, 256, 240, 44}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_fifo_signal{
|
||||||
|
Message::ID::RequestSignal,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||||
|
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||||
|
ready_signal = true;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_tx_progress{
|
||||||
|
Message::ID::TXProgress,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||||
|
this->on_tx_progress(message.progress);
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_replay_thread_done{
|
||||||
|
Message::ID::ReplayThreadDone,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||||
|
if (message.return_code == ReplayThread::END_OF_FILE) {
|
||||||
|
if (chaos_mode) {
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
ready_signal = false;
|
||||||
|
lfsr_v = lfsr_iterate(lfsr_v);
|
||||||
|
size_t random_index = lfsr_v % file_list.size();
|
||||||
|
menu_view.set_highlighted(random_index);
|
||||||
|
chThdSleepMilliseconds(100);
|
||||||
|
start_tx(random_index);
|
||||||
|
} else {
|
||||||
|
thread_sync_complete = true;
|
||||||
|
stop_tx();
|
||||||
|
}
|
||||||
|
} else if (message.return_code == ReplayThread::READ_ERROR) {
|
||||||
|
file_error(file_list[menu_view.highlighted_index()], "Read error during playback");
|
||||||
|
if (chaos_mode) {
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
ready_signal = false;
|
||||||
|
lfsr_v = lfsr_iterate(lfsr_v);
|
||||||
|
size_t random_index = lfsr_v % file_list.size();
|
||||||
|
menu_view.set_highlighted(random_index);
|
||||||
|
chThdSleepMilliseconds(100);
|
||||||
|
start_tx(random_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::cvs_spam
|
34
firmware/application/external/cvs_spam/main.cpp
vendored
Normal file
34
firmware/application/external/cvs_spam/main.cpp
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||||
|
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||||
|
// If you can read this, you're a nerd. :P
|
||||||
|
// Come join us at https://discord.gg/thepiratesreborn
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "cvs_spam.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::cvs_spam {
|
||||||
|
void initialize_app(NavigationView& nav) {
|
||||||
|
nav.push<CVSSpamView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::cvs_spam
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::cvs_spam::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "CVS Spam",
|
||||||
|
/*.bitmap_data = */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
|
||||||
|
/*.icon_color = */ ui::Color::red().v,
|
||||||
|
/*.menu_location = */ app_location_t::TX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'R', 'E', 'P'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
62
firmware/application/external/external.cmake
vendored
62
firmware/application/external/external.cmake
vendored
@ -96,6 +96,56 @@ set(EXTCPPSRC
|
|||||||
#sstvtx
|
#sstvtx
|
||||||
external/sstvtx/main.cpp
|
external/sstvtx/main.cpp
|
||||||
external/sstvtx/ui_sstvtx.cpp
|
external/sstvtx/ui_sstvtx.cpp
|
||||||
|
|
||||||
|
#random
|
||||||
|
external/random_password/main.cpp
|
||||||
|
external/random_password/ui_random_password.cpp
|
||||||
|
external/random_password/sha512.cpp
|
||||||
|
external/random_password/sha512.h
|
||||||
|
|
||||||
|
#acars
|
||||||
|
external/acars_rx/main.cpp
|
||||||
|
external/acars_rx/acars_app.cpp
|
||||||
|
|
||||||
|
#shoppingcart_lock
|
||||||
|
external/shoppingcart_lock/main.cpp
|
||||||
|
external/shoppingcart_lock/shoppingcart_lock.cpp
|
||||||
|
|
||||||
|
#ookbrute
|
||||||
|
external/ookbrute/main.cpp
|
||||||
|
external/ookbrute/ui_ookbrute.cpp
|
||||||
|
|
||||||
|
#ook_editor
|
||||||
|
external/ook_editor/main.cpp
|
||||||
|
external/ook_editor/ui_ook_editor.cpp
|
||||||
|
|
||||||
|
#cvs_spam
|
||||||
|
external/cvs_spam/main.cpp
|
||||||
|
external/cvs_spam/cvs_spam.cpp
|
||||||
|
|
||||||
|
#flippertx
|
||||||
|
external/flippertx/main.cpp
|
||||||
|
external/flippertx/ui_flippertx.cpp
|
||||||
|
|
||||||
|
#remote
|
||||||
|
external/remote/main.cpp
|
||||||
|
external/remote/ui_remote.cpp
|
||||||
|
|
||||||
|
#mcu_temperature
|
||||||
|
external/mcu_temperature/main.cpp
|
||||||
|
external/mcu_temperature/mcu_temperature.cpp
|
||||||
|
|
||||||
|
#fmradio
|
||||||
|
external/fmradio/main.cpp
|
||||||
|
external/fmradio/ui_fmradio.cpp
|
||||||
|
|
||||||
|
#tuner
|
||||||
|
external/tuner/main.cpp
|
||||||
|
external/tuner/ui_tuner.cpp
|
||||||
|
|
||||||
|
#metronome
|
||||||
|
external/metronome/main.cpp
|
||||||
|
external/metronome/ui_metronome.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(EXTAPPLIST
|
set(EXTAPPLIST
|
||||||
@ -117,9 +167,21 @@ set(EXTAPPLIST
|
|||||||
foxhunt_rx
|
foxhunt_rx
|
||||||
audio_test
|
audio_test
|
||||||
wardrivemap
|
wardrivemap
|
||||||
|
cvs_spam
|
||||||
tpmsrx
|
tpmsrx
|
||||||
protoview
|
protoview
|
||||||
adsbtx
|
adsbtx
|
||||||
morse_tx
|
morse_tx
|
||||||
sstvtx
|
sstvtx
|
||||||
|
random_password
|
||||||
|
#acars_rx
|
||||||
|
ookbrute
|
||||||
|
ook_editor
|
||||||
|
shoppingcart_lock
|
||||||
|
flippertx
|
||||||
|
remote
|
||||||
|
mcu_temperature
|
||||||
|
fmradio
|
||||||
|
tuner
|
||||||
|
metronome
|
||||||
)
|
)
|
||||||
|
82
firmware/application/external/external.ld
vendored
82
firmware/application/external/external.ld
vendored
@ -46,6 +46,18 @@ MEMORY
|
|||||||
ram_external_app_adsbtx(rwx) : org = 0xADC50000, len = 32k
|
ram_external_app_adsbtx(rwx) : org = 0xADC50000, len = 32k
|
||||||
ram_external_app_morse_tx(rwx) : org = 0xADC60000, len = 32k
|
ram_external_app_morse_tx(rwx) : org = 0xADC60000, len = 32k
|
||||||
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
|
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
|
||||||
|
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
|
||||||
|
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
|
||||||
|
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
|
||||||
|
ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k
|
||||||
|
ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k
|
||||||
|
ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k
|
||||||
|
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
|
||||||
|
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
|
||||||
|
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
|
||||||
|
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
|
||||||
|
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
|
||||||
|
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTIONS
|
SECTIONS
|
||||||
@ -164,7 +176,6 @@ SECTIONS
|
|||||||
*(*ui*external_app*tpmsrx*);
|
*(*ui*external_app*tpmsrx*);
|
||||||
} > ram_external_app_tpmsrx
|
} > ram_external_app_tpmsrx
|
||||||
|
|
||||||
|
|
||||||
.external_app_protoview : ALIGN(4) SUBALIGN(4)
|
.external_app_protoview : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_protoview.application_information));
|
KEEP(*(.external_app.app_protoview.application_information));
|
||||||
@ -190,6 +201,75 @@ SECTIONS
|
|||||||
*(*ui*external_app*sstvtx*);
|
*(*ui*external_app*sstvtx*);
|
||||||
} > ram_external_app_sstvtx
|
} > ram_external_app_sstvtx
|
||||||
|
|
||||||
|
.external_app_random_password : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_random_password.application_information));
|
||||||
|
*(*ui*external_app*random_password*);
|
||||||
|
} > ram_external_app_random_password
|
||||||
|
|
||||||
|
.external_app_acars_rx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_acars_rx.application_information));
|
||||||
|
*(*ui*external_app*acars_rx*);
|
||||||
|
} > ram_external_app_acars_rx
|
||||||
|
|
||||||
|
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
|
||||||
|
*(*ui*external_app*shoppingcart_lock*);
|
||||||
|
} > ram_external_app_shoppingcart_lock
|
||||||
|
|
||||||
|
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_cvs_spam.application_information));
|
||||||
|
*(*ui*external_app*cvs_spam*);
|
||||||
|
} > ram_external_app_cvs_spam
|
||||||
|
|
||||||
|
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_ookbrute.application_information));
|
||||||
|
*(*ui*external_app*ookbrute*);
|
||||||
|
} > ram_external_app_ookbrute
|
||||||
|
|
||||||
|
.external_app_ook_editor : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_ook_editor.application_information));
|
||||||
|
*(*ui*external_app*ook_editor*);
|
||||||
|
} > ram_external_app_ook_editor
|
||||||
|
|
||||||
|
.external_app_flippertx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_flippertx.application_information));
|
||||||
|
*(*ui*external_app*flippertx*);
|
||||||
|
} > ram_external_app_flippertx
|
||||||
|
|
||||||
|
.external_app_remote : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_remote.application_information));
|
||||||
|
*(*ui*external_app*remote*);
|
||||||
|
} > ram_external_app_remote
|
||||||
|
|
||||||
|
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
||||||
|
*(*ui*external_app*mcu_temperature*);
|
||||||
|
} > ram_external_app_mcu_temperature
|
||||||
|
|
||||||
|
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_fmradio.application_information));
|
||||||
|
*(*ui*external_app*fmradio*);
|
||||||
|
} > ram_external_app_fmradio
|
||||||
|
|
||||||
|
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_tuner.application_information));
|
||||||
|
*(*ui*external_app*tuner*);
|
||||||
|
} > ram_external_app_tuner
|
||||||
|
|
||||||
|
.external_app_metronome : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_metronome.application_information));
|
||||||
|
*(*ui*external_app*metronome*);
|
||||||
|
} > ram_external_app_metronome
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_extsensors.application_information"),
|
|||||||
},
|
},
|
||||||
/*.icon_color = */ ui::Color::orange().v,
|
/*.icon_color = */ ui::Color::orange().v,
|
||||||
/*.menu_location = */ app_location_t::DEBUG,
|
/*.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_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "i2cdevmanager.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
@ -29,7 +30,7 @@ using namespace ui;
|
|||||||
namespace ui::external_app::extsensors {
|
namespace ui::external_app::extsensors {
|
||||||
|
|
||||||
void ExtSensorsView::focus() {
|
void ExtSensorsView::focus() {
|
||||||
text_info.focus();
|
console.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtSensorsView::ExtSensorsView(NavigationView& nav)
|
ExtSensorsView::ExtSensorsView(NavigationView& nav)
|
||||||
@ -39,10 +40,33 @@ ExtSensorsView::ExtSensorsView(NavigationView& nav)
|
|||||||
&text_gps,
|
&text_gps,
|
||||||
&text_orientation,
|
&text_orientation,
|
||||||
&text_envl1,
|
&text_envl1,
|
||||||
&text_envl2});
|
&text_envl2,
|
||||||
|
&text_envl3,
|
||||||
|
&console});
|
||||||
|
|
||||||
|
prev_scan_int = i2cdev::I2CDevManager::get_autoscan_interval();
|
||||||
|
refreshi2c();
|
||||||
|
i2cdev::I2CDevManager::set_autoscan_interval(3); // scan each 3 sec for new i2c devices
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtSensorsView::on_new_dev() {
|
||||||
|
refreshi2c();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtSensorsView::refreshi2c() {
|
||||||
|
console.clear(true);
|
||||||
|
console.writeln("Found I2C devices:");
|
||||||
|
auto addrlist = i2cdev::I2CDevManager::get_gev_list_by_addr();
|
||||||
|
for (size_t i = 0; i < addrlist.size(); ++i) {
|
||||||
|
console.write("0x");
|
||||||
|
console.write(to_string_hex(addrlist[i]));
|
||||||
|
console.write(", ");
|
||||||
|
}
|
||||||
|
if (addrlist.size() == 0) console.writeln("No I2C devs found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtSensorsView::~ExtSensorsView() {
|
ExtSensorsView::~ExtSensorsView() {
|
||||||
|
i2cdev::I2CDevManager::set_autoscan_interval(prev_scan_int);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtSensorsView::on_any() {
|
void ExtSensorsView::on_any() {
|
||||||
@ -78,9 +102,14 @@ void ExtSensorsView::on_environment(const EnvironmentDataMessage* msg) {
|
|||||||
tmp += "C";
|
tmp += "C";
|
||||||
tmp += "; H: " + to_string_decimal(msg->humidity, 1) + "%"; // humidity
|
tmp += "; H: " + to_string_decimal(msg->humidity, 1) + "%"; // humidity
|
||||||
text_envl1.set(tmp);
|
text_envl1.set(tmp);
|
||||||
tmp = "P: " + to_string_decimal(msg->pressure, 1) + " hPa; L:"; // pressure
|
tmp = "P: " + to_string_decimal(msg->pressure, 1) + " hPa"; // pressure
|
||||||
tmp += to_string_dec_int(msg->light) + " LUX"; // light
|
|
||||||
text_envl2.set(tmp);
|
text_envl2.set(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExtSensorsView::on_light(const LightDataMessage* msg) {
|
||||||
|
on_any();
|
||||||
|
std::string tmp = "L: " + to_string_dec_int(msg->light) + " LUX";
|
||||||
|
text_envl3.set(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui::external_app::extsensors
|
} // namespace ui::external_app::extsensors
|
@ -53,6 +53,7 @@ class ExtSensorsView : public View {
|
|||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
|
|
||||||
bool has_data = false;
|
bool has_data = false;
|
||||||
|
uint16_t prev_scan_int = 0;
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 3 * 16}, "GPS:", Theme::getInstance()->fg_light->foreground},
|
{{0 * 8, 3 * 16}, "GPS:", Theme::getInstance()->fg_light->foreground},
|
||||||
@ -64,9 +65,15 @@ class ExtSensorsView : public View {
|
|||||||
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
|
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
|
||||||
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
|
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
|
||||||
Text text_envl2{{1 * 8, 9 * 16, 24 * 8, 16}, "-"};
|
Text text_envl2{{1 * 8, 9 * 16, 24 * 8, 16}, "-"};
|
||||||
|
Text text_envl3{{1 * 8, 11 * 16, 24 * 8, 16}, "-"};
|
||||||
|
Console console{
|
||||||
|
{1, 13 * 16, screen_width - 1, screen_height - 13 * 16}};
|
||||||
|
|
||||||
|
void refreshi2c();
|
||||||
|
void on_new_dev();
|
||||||
void on_any();
|
void on_any();
|
||||||
|
|
||||||
|
void on_light(const LightDataMessage* msg);
|
||||||
void on_gps(const GPSPosDataMessage* msg);
|
void on_gps(const GPSPosDataMessage* msg);
|
||||||
void on_orientation(const OrientationDataMessage* msg);
|
void on_orientation(const OrientationDataMessage* msg);
|
||||||
void on_environment(const EnvironmentDataMessage* msg);
|
void on_environment(const EnvironmentDataMessage* msg);
|
||||||
@ -90,6 +97,20 @@ class ExtSensorsView : public View {
|
|||||||
const auto message = static_cast<const EnvironmentDataMessage*>(p);
|
const auto message = static_cast<const EnvironmentDataMessage*>(p);
|
||||||
this->on_environment(message);
|
this->on_environment(message);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_light{
|
||||||
|
Message::ID::LightData,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const LightDataMessage*>(p);
|
||||||
|
this->on_light(message);
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_dev{
|
||||||
|
Message::ID::I2CDevListChanged,
|
||||||
|
[this](Message* const p) {
|
||||||
|
(void)p; // make compiler happy
|
||||||
|
this->on_new_dev();
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
}; // namespace ui::external_app::extsensors
|
}; // namespace ui::external_app::extsensors
|
||||||
|
|
||||||
|
83
firmware/application/external/flippertx/main.cpp
vendored
Normal file
83
firmware/application/external/flippertx/main.cpp
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Bernd Herzog
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_flippertx.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::flippertx {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<FlipperTxView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::flippertx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_flippertx.application_information"), used)) application_information_t _application_information_flippertx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::flippertx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "FlipperTx",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x30,
|
||||||
|
0x0C,
|
||||||
|
0x30,
|
||||||
|
0x0C,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x70,
|
||||||
|
0x0D,
|
||||||
|
0xB0,
|
||||||
|
0x0E,
|
||||||
|
0x70,
|
||||||
|
0x0D,
|
||||||
|
0xB0,
|
||||||
|
0x0E,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::orange().v,
|
||||||
|
/*.menu_location = */ app_location_t::TX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_ookstreaming */ {'P', 'O', 'S', 'K'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
285
firmware/application/external/flippertx/ui_flippertx.cpp
vendored
Normal file
285
firmware/application/external/flippertx/ui_flippertx.cpp
vendored
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui_flippertx.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "rtc_time.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "buffer_exchange.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
using namespace ui;
|
||||||
|
|
||||||
|
namespace ui::external_app::flippertx {
|
||||||
|
|
||||||
|
void FlipperTxView::focus() {
|
||||||
|
button_browse.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperTxView::set_ready() {
|
||||||
|
ready_signal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipperTxView::FlipperTxView(NavigationView& nav)
|
||||||
|
: nav_{nav} {
|
||||||
|
add_children({&button_startstop,
|
||||||
|
&field_frequency,
|
||||||
|
&tx_view,
|
||||||
|
&field_filename,
|
||||||
|
&button_browse});
|
||||||
|
|
||||||
|
button_startstop.on_select = [this](Button&) {
|
||||||
|
if (is_running) {
|
||||||
|
stop();
|
||||||
|
} else {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button_browse.on_select = [this](Button&) {
|
||||||
|
auto open_view = nav_.push<FileLoadView>(".sub");
|
||||||
|
open_view->push_dir(subghz_dir);
|
||||||
|
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||||
|
if (on_file_changed(new_file_path)) {
|
||||||
|
; // tif (button_startstop.parent()) button_startstop.focus(); parent_ is null error.....
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlipperTxView::on_file_changed(std::filesystem::path new_file_path) {
|
||||||
|
submeta = read_flippersub_file(new_file_path);
|
||||||
|
filename = "";
|
||||||
|
if (!submeta.is_valid()) {
|
||||||
|
field_filename.set_text("File: err, bad file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
proto = submeta.value().protocol;
|
||||||
|
if (proto != FLIPPER_PROTO_RAW && proto != FLIPPER_PROTO_BINRAW) { // temp disabled
|
||||||
|
field_filename.set_text("File: err, not supp. proto");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
preset = submeta.value().preset;
|
||||||
|
if (preset != FLIPPER_PRESET_OOK) {
|
||||||
|
field_filename.set_text("File: err, not supp. preset");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
te = submeta.value().te;
|
||||||
|
binraw_bit_count = submeta.value().binraw_bit_count;
|
||||||
|
/*if (binraw_bit_count >= 512 * 800) {
|
||||||
|
field_filename.set_text("File: err, too long"); // should stream, but not in this scope YET
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
|
field_filename.set_text("File: " + new_file_path.string());
|
||||||
|
filename = new_file_path;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperTxView::stop() {
|
||||||
|
if (is_running && replay_thread) replay_thread.reset();
|
||||||
|
is_running = false;
|
||||||
|
ready_signal = false;
|
||||||
|
|
||||||
|
transmitter_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlipperTxView::start() {
|
||||||
|
if (filename.empty()) return false;
|
||||||
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||||
|
transmitter_model.enable();
|
||||||
|
button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
|
||||||
|
// start thread
|
||||||
|
replay_thread = std::make_unique<FlipperPlayThread>(
|
||||||
|
filename,
|
||||||
|
1024, 4,
|
||||||
|
&ready_signal,
|
||||||
|
submeta.value().protocol,
|
||||||
|
submeta.value().te,
|
||||||
|
[](uint32_t return_code) {
|
||||||
|
ReplayThreadDoneMessage message{return_code};
|
||||||
|
EventDispatcher::send_message(message);
|
||||||
|
});
|
||||||
|
is_running = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlipperTxView::on_tx_progress(const bool done) {
|
||||||
|
if (done) {
|
||||||
|
if (is_running) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipperTxView::~FlipperTxView() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipperPlayThread::FlipperPlayThread(
|
||||||
|
std::filesystem::path filename,
|
||||||
|
size_t read_size,
|
||||||
|
size_t buffer_count,
|
||||||
|
bool* ready_signal,
|
||||||
|
FlipperProto proto,
|
||||||
|
uint16_t te,
|
||||||
|
std::function<void(uint32_t return_code)> terminate_callback)
|
||||||
|
: config{read_size, buffer_count},
|
||||||
|
filename{filename},
|
||||||
|
ready_sig{ready_signal},
|
||||||
|
proto{proto},
|
||||||
|
te{te},
|
||||||
|
terminate_callback{std::move(terminate_callback)} {
|
||||||
|
thread = chThdCreateFromHeap(NULL, 6 * 1024, NORMALPRIO + 10, FlipperPlayThread::static_fn, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipperPlayThread::~FlipperPlayThread() {
|
||||||
|
if (thread) {
|
||||||
|
chThdTerminate(thread);
|
||||||
|
if (thread->p_refs) chThdWait(thread);
|
||||||
|
thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_t FlipperPlayThread::static_fn(void* arg) {
|
||||||
|
auto obj = static_cast<FlipperPlayThread*>(arg);
|
||||||
|
const auto return_code = obj->run();
|
||||||
|
if (obj->terminate_callback) {
|
||||||
|
obj->terminate_callback(return_code);
|
||||||
|
}
|
||||||
|
chThdExit(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t FlipperPlayThread::run() {
|
||||||
|
BasebandReplay replay{&config};
|
||||||
|
BufferExchange buffers{&config};
|
||||||
|
StreamBuffer* prefill_buffer{nullptr};
|
||||||
|
int32_t read_result = 0;
|
||||||
|
// assume it is ok, since prev we checked it.
|
||||||
|
// open the sub file
|
||||||
|
File f; // don't worry, destructor will close it.
|
||||||
|
auto error = f.open(filename);
|
||||||
|
if (error) return READ_ERROR;
|
||||||
|
// seek to the first data
|
||||||
|
if (proto == FLIPPER_PROTO_RAW)
|
||||||
|
seek_flipper_raw_first_data(f);
|
||||||
|
else {
|
||||||
|
seek_flipper_binraw_first_data(f);
|
||||||
|
}
|
||||||
|
// Wait for FIFOs to be allocated in baseband
|
||||||
|
// Wait for _view to tell us that the buffers are ready
|
||||||
|
while (!(*ready_sig) && !chThdShouldTerminate()) {
|
||||||
|
chThdSleep(100);
|
||||||
|
};
|
||||||
|
uint8_t readall = 0;
|
||||||
|
int32_t endsignals[3] = {0, 42069, 613379}; // unlikely a valid data
|
||||||
|
// While empty buffers fifo is not empty...
|
||||||
|
while (!buffers.empty() && !chThdShouldTerminate()) {
|
||||||
|
prefill_buffer = buffers.get_prefill();
|
||||||
|
|
||||||
|
if (prefill_buffer == nullptr) {
|
||||||
|
buffers.put_app(prefill_buffer);
|
||||||
|
} else {
|
||||||
|
size_t c = 0;
|
||||||
|
for (c = 0; c < config.read_size / 4;) {
|
||||||
|
read_result = 0;
|
||||||
|
if (0 == readall) {
|
||||||
|
if (proto == FLIPPER_PROTO_RAW) {
|
||||||
|
auto data = read_flipper_raw_next_data(f);
|
||||||
|
if (!data.is_valid()) {
|
||||||
|
((int32_t*)prefill_buffer->data())[c] = endsignals[++readall];
|
||||||
|
} else {
|
||||||
|
read_result = data.value();
|
||||||
|
((int32_t*)prefill_buffer->data())[c] = read_result;
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
|
||||||
|
} else { // BINRAW
|
||||||
|
auto data = read_flipper_binraw_next_data(f);
|
||||||
|
if (!data.is_valid())
|
||||||
|
readall = 1;
|
||||||
|
else {
|
||||||
|
read_result = data.value();
|
||||||
|
// add 8 data
|
||||||
|
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||||
|
((int32_t*)prefill_buffer->data())[c + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
((int32_t*)prefill_buffer->data())[c] = endsignals[readall];
|
||||||
|
if (readall == 1) readall++;
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefill_buffer->set_size(config.read_size);
|
||||||
|
buffers.put(prefill_buffer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
baseband::set_fifo_data(nullptr);
|
||||||
|
if (readall) return END_OF_FILE;
|
||||||
|
|
||||||
|
while (!chThdShouldTerminate()) {
|
||||||
|
auto buffer = buffers.get();
|
||||||
|
int32_t read_result = 0;
|
||||||
|
for (size_t d = 0; d < buffer->capacity() / 4; d++) {
|
||||||
|
read_result = -133469;
|
||||||
|
if (!readall) {
|
||||||
|
if (proto == FLIPPER_PROTO_RAW) {
|
||||||
|
auto data = read_flipper_raw_next_data(f);
|
||||||
|
if (!data.is_valid()) {
|
||||||
|
readall = 1;
|
||||||
|
} else {
|
||||||
|
read_result = data.value();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto data = read_flipper_binraw_next_data(f);
|
||||||
|
if (!data.is_valid())
|
||||||
|
readall = 1;
|
||||||
|
else {
|
||||||
|
read_result = data.value();
|
||||||
|
// add 8 data
|
||||||
|
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||||
|
((int32_t*)prefill_buffer->data())[d + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
|
||||||
|
}
|
||||||
|
d += 7;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (readall >= 1 && readall <= 2) {
|
||||||
|
read_result = endsignals[readall++];
|
||||||
|
}
|
||||||
|
((int32_t*)buffer->data())[d] = read_result;
|
||||||
|
}
|
||||||
|
buffer->set_size(buffer->capacity());
|
||||||
|
buffers.put(buffer);
|
||||||
|
if (readall == 3) return END_OF_FILE;
|
||||||
|
}
|
||||||
|
return TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::flippertx
|
171
firmware/application/external/flippertx/ui_flippertx.hpp
vendored
Normal file
171
firmware/application/external/flippertx/ui_flippertx.hpp
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UI_flippertx_H__
|
||||||
|
#define __UI_flippertx_H__
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_language.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_transmitter.hpp"
|
||||||
|
#include "ui_freq_field.hpp"
|
||||||
|
#include "app_settings.hpp"
|
||||||
|
#include "radio_state.hpp"
|
||||||
|
#include "utility.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
#include "metadata_file.hpp"
|
||||||
|
#include "flipper_subfile.hpp"
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
|
using namespace ui;
|
||||||
|
|
||||||
|
namespace ui::external_app::flippertx {
|
||||||
|
|
||||||
|
#define OOK_SAMPLERATE 2280000U
|
||||||
|
class FlipperPlayThread;
|
||||||
|
class FlipperTxView : public View {
|
||||||
|
public:
|
||||||
|
FlipperTxView(NavigationView& nav);
|
||||||
|
~FlipperTxView();
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
|
|
||||||
|
std::string title() const override {
|
||||||
|
return "FlipperTx";
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
NavigationView& nav_;
|
||||||
|
TxRadioState radio_state_{
|
||||||
|
433920000 /* frequency */,
|
||||||
|
1750000 /* bandwidth */,
|
||||||
|
OOK_SAMPLERATE /* sampling rate */
|
||||||
|
};
|
||||||
|
|
||||||
|
TxFrequencyField field_frequency{
|
||||||
|
{0 * 8, 0 * 16},
|
||||||
|
nav_};
|
||||||
|
TransmitterView2 tx_view{
|
||||||
|
{11 * 8, 0 * 16},
|
||||||
|
/*short_ui*/ true};
|
||||||
|
|
||||||
|
app_settings::SettingsManager settings_{
|
||||||
|
"tx_flippertx", app_settings::Mode::TX};
|
||||||
|
|
||||||
|
TextField field_filename{
|
||||||
|
{0, 2 * 16, 300, 1 * 8},
|
||||||
|
"File: -"};
|
||||||
|
|
||||||
|
Button button_startstop{
|
||||||
|
{1, 6 * 16, 96, 24},
|
||||||
|
LanguageHelper::currentMessages[LANG_START]};
|
||||||
|
|
||||||
|
Button button_browse{
|
||||||
|
{1, 3 * 16 + 3, 96, 24},
|
||||||
|
LanguageHelper::currentMessages[LANG_BROWSE]};
|
||||||
|
|
||||||
|
bool is_running{false};
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void set_ready();
|
||||||
|
void on_tx_progress(const bool done);
|
||||||
|
bool on_file_changed(std::filesystem::path new_file_path);
|
||||||
|
|
||||||
|
std::filesystem::path filename = {};
|
||||||
|
FlipperProto proto = FLIPPER_PROTO_UNSUPPORTED;
|
||||||
|
FlipperPreset preset = FLIPPER_PRESET_UNK;
|
||||||
|
uint16_t te = 0; // for binraw
|
||||||
|
uint32_t binraw_bit_count = 0;
|
||||||
|
bool ready_signal = false;
|
||||||
|
|
||||||
|
std::unique_ptr<FlipperPlayThread> replay_thread{};
|
||||||
|
Optional<flippersub_metadata> submeta{};
|
||||||
|
|
||||||
|
const std::filesystem::path subghz_dir = u"subghz";
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_tx_progress{
|
||||||
|
Message::ID::TXProgress,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||||
|
this->on_tx_progress(message.done);
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_fifo_signal{
|
||||||
|
Message::ID::RequestSignal,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||||
|
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||||
|
this->set_ready();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_replay_thread_done{
|
||||||
|
Message::ID::ReplayThreadDone,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
// const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||||
|
(void)p;
|
||||||
|
// stop(); //don't stop for now, stop when i get the tx finished msg
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BasebandReplay {
|
||||||
|
BasebandReplay(ReplayConfig* const config) {
|
||||||
|
baseband::replay_start(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
~BasebandReplay() {
|
||||||
|
// baseband::replay_stop(); // wont, since we need to send out that is in the buffer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FlipperPlayThread {
|
||||||
|
public:
|
||||||
|
FlipperPlayThread(
|
||||||
|
std::filesystem::path,
|
||||||
|
size_t read_size,
|
||||||
|
size_t buffer_count,
|
||||||
|
bool* ready_signal,
|
||||||
|
FlipperProto proto,
|
||||||
|
uint16_t te,
|
||||||
|
std::function<void(uint32_t return_code)> terminate_callback);
|
||||||
|
~FlipperPlayThread();
|
||||||
|
|
||||||
|
FlipperPlayThread(const FlipperPlayThread&) = delete;
|
||||||
|
FlipperPlayThread(FlipperPlayThread&&) = delete;
|
||||||
|
FlipperPlayThread& operator=(const FlipperPlayThread&) = delete;
|
||||||
|
FlipperPlayThread& operator=(FlipperPlayThread&&) = delete;
|
||||||
|
|
||||||
|
const ReplayConfig& state() const {
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FlipperPlayThread_return {
|
||||||
|
READ_ERROR = 0,
|
||||||
|
END_OF_FILE,
|
||||||
|
TERMINATED
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
ReplayConfig config;
|
||||||
|
std::filesystem::path filename;
|
||||||
|
bool* ready_sig;
|
||||||
|
FlipperProto proto;
|
||||||
|
uint16_t te;
|
||||||
|
std::function<void(uint32_t return_code)> terminate_callback;
|
||||||
|
Thread* thread{nullptr};
|
||||||
|
|
||||||
|
static msg_t static_fn(void* arg);
|
||||||
|
|
||||||
|
uint32_t run();
|
||||||
|
};
|
||||||
|
}; // namespace ui::external_app::flippertx
|
||||||
|
|
||||||
|
#endif /*__UI_flippertx_H__*/
|
83
firmware/application/external/fmradio/main.cpp
vendored
Normal file
83
firmware/application/external/fmradio/main.cpp
vendored
Normal file
@ -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
|
||||||
|
};
|
||||||
|
}
|
168
firmware/application/external/fmradio/ui_fmradio.cpp
vendored
Normal file
168
firmware/application/external/fmradio/ui_fmradio.cpp
vendored
Normal file
@ -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
|
150
firmware/application/external/fmradio/ui_fmradio.hpp
vendored
Normal file
150
firmware/application/external/fmradio/ui_fmradio.hpp
vendored
Normal file
@ -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,
|
/*.icon_color = */ ui::Color::cyan().v,
|
||||||
/*.menu_location = */ app_location_t::DEBUG,
|
/*.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_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::RX,
|
/*.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_tag = portapack::spi_flash::image_tag_am_audio */ {'P', 'A', 'M', 'A'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_gpssim */ {'P', 'G', 'P', 'S'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.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,
|
/*.icon_color = */ ui::Color::orange().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
1
firmware/application/external/lcr/main.cpp
vendored
1
firmware/application/external/lcr/main.cpp
vendored
@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lcr.application_information"), used))
|
|||||||
},
|
},
|
||||||
/*.icon_color = */ ui::Color::yellow().v,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
1
firmware/application/external/lge/main.cpp
vendored
1
firmware/application/external/lge/main.cpp
vendored
@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lge.application_information"), used))
|
|||||||
},
|
},
|
||||||
/*.icon_color = */ ui::Color::yellow().v,
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
/*.menu_location = */ app_location_t::TX,
|
/*.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_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
|
||||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
83
firmware/application/external/mcu_temperature/main.cpp
vendored
Normal file
83
firmware/application/external/mcu_temperature/main.cpp
vendored
Normal file
@ -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
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user