mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-18 21:53:59 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 167f89df0b | |||
| 1fcd87756e | |||
| 824b53f7d4 | |||
| 86584feb16 | |||
| 47c94dbf26 | |||
| b048b8f4f1 | |||
| 9067e007b6 | |||
| 367eaf54c0 | |||
| de30cbfe1b | |||
| a4d91f12ef | |||
| c4f32ac436 | |||
| efe214167d | |||
| 483a100100 | |||
| 8561ff32b3 | |||
| 87eb8e5863 | |||
| d11669f711 | |||
| 7513cd46fc | |||
| d671455f79 | |||
| 42122a739c | |||
| 03fe508aae | |||
| e3eafbeb66 | |||
| e3b3fa2fc8 | |||
| 5f0ae22f66 | |||
| f9dd28f31d | |||
| b99cec5111 | |||
| fe2ba80a10 | |||
| b5d84fcc4d | |||
| 7481aefe9f | |||
| 6b641c8a5d | |||
| 2ad34bbc09 | |||
| 9c74a1f392 | |||
| abadf35e06 | |||
| 288e87537a | |||
| d25899de10 | |||
| 8a16ec829c | |||
| f6d6e3be4c | |||
| c16bac24c7 | |||
| 4e6a2cc934 | |||
| cafe62564e | |||
| 960e5b8329 | |||
| f5a5e8e5e8 | |||
| d8a1e2530a | |||
| b84a424875 | |||
| 62d0e598d4 | |||
| a93f2ff583 | |||
| e2586ceb22 | |||
| 5a2b0cdc2a | |||
| f852900cd0 | |||
| 9ab367f2e0 | |||
| c7a0846645 | |||
| 569bcaad22 | |||
| 6b5955e0b5 | |||
| 9042d1c433 | |||
| 378b2808ba | |||
| 5b363b79aa | |||
| 0d51787099 | |||
| 8f5b25b10d | |||
| 4e56d0f420 | |||
| 2bb3b415d1 | |||
| 6e0394fb6b | |||
| da21ea7866 | |||
| 2c434946cd | |||
| f3aee44e3b | |||
| 165c190d92 | |||
| 8f9246c1ea | |||
| f1d52ae78f |
@@ -1,58 +1,80 @@
|
|||||||
<!--
|
<!--
|
||||||
👋 Thank you for your contribution!
|
👋 Thank you for your contribution!
|
||||||
|
|
||||||
⚠️ Before you start, please review our Contributing Guidelines:
|
⚠️ Before you start, please review our Contributing Guidelines:
|
||||||
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines
|
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Brief description of what you did
|
## Brief description of what you did
|
||||||
|
<!--
|
||||||
|
Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
|
||||||
|
Don't forget to describe how maintainers can test your modifications!
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
|
|
||||||
Don't forget to describe how maintainers can test your modifications! -->
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Proof that your changes work
|
## Proof that your changes work
|
||||||
|
<!--
|
||||||
|
1. PRs without proof will not be merged. Please provide evidence for every section below that applies to your change.
|
||||||
|
2. Trusted contributors: If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note.
|
||||||
|
3. First-time contributors must provide full proof. See the Contributing Guidelines(https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and Trusted Contributors (https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> ⚠️ **PRs without proof will not be merged.** Please provide evidence for every section below that applies to your change.
|
|
||||||
>
|
|
||||||
> 🛈 **Trusted contributors:** If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note. First-time contributors must provide full proof. See the [Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and [Trusted Contributors](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
|
|
||||||
|
|
||||||
### 🖥️ Proof it compiles
|
### 🖥️ Proof it compiles
|
||||||
|
<!--
|
||||||
Attach a log snippet showing a successful build from the final firmware size/space summary in the build output. Example: `Space remaining in flash ROM: 19552 bytes ( 1.9 % )`.
|
Attach a log snippet showing a successful build from the final firmware size/space summary in the build output. Example: `Space remaining in flash ROM: 19552 bytes ( 1.9 % )`.
|
||||||
|
Paste log below.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Paste log below -->
|
|
||||||
|
|
||||||
### 📱 Proof of testing on a real device
|
### 📱 Proof of testing on a real device
|
||||||
|
<!--
|
||||||
|
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. Emulator or simulator output is not a substitute for real-device testing.
|
||||||
|
Paste photo / video / screenshot below
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. **Emulator or simulator output is not a substitute for real-device testing.**
|
|
||||||
|
|
||||||
<!-- Paste photo / video / screenshot below -->
|
|
||||||
|
|
||||||
### 📡 Proof against a real emitter/receiver (if applicable)
|
### 📡 Proof against a real emitter/receiver (if applicable)
|
||||||
|
<!--
|
||||||
|
1. If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
|
||||||
|
2. If this section does not apply to your PR, write `N/A` below and briefly explain why.
|
||||||
|
3. Paste RF evidence, or write N/A with a short justification
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
|
|
||||||
|
|
||||||
If this section does not apply to your PR, write `N/A` below and briefly explain why.
|
|
||||||
|
|
||||||
<!-- Paste RF evidence, or write N/A with a short justification -->
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 Wiki documentation commitment
|
## 📚 Wiki documentation commitment
|
||||||
|
<!--
|
||||||
**By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the [project wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki).**
|
By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the project wiki(https://github.com/portapack-mayhem/mayhem-firmware/wiki).
|
||||||
|
|
||||||
Your wiki article should include:
|
Your wiki article should include:
|
||||||
|
- A screenshot of the main app screen (strongly recommended)
|
||||||
- A **screenshot of the main app screen** (strongly recommended)
|
- A clear description of what the app does
|
||||||
- A clear **description** of what the app does
|
- An explanation of the controls and UI elements
|
||||||
- An explanation of the **controls** and UI elements
|
- Any known limitations, quirks, or caveats
|
||||||
- Any known **limitations**, quirks, or caveats
|
|
||||||
- Anything else a user should know to use the app effectively (dependencies, required hardware, file formats, etc.)
|
- Anything else a user should know to use the app effectively (dependencies, required hardware, file formats, etc.)
|
||||||
|
- If you are modifying an existing app, make sure the wiki page reflects your changes.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] I will (or already) create(d) wiki document for my newly added feature
|
||||||
|
|
||||||
If you are modifying an existing app, make sure the wiki page reflects your changes.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -83,3 +83,4 @@ venv/
|
|||||||
# generated bitmap arr file
|
# generated bitmap arr file
|
||||||
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
|
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
|
||||||
/firmware/tools/bitmap.hpp
|
/firmware/tools/bitmap.hpp
|
||||||
|
tmp/*
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# PortaPack Mayhem – Agent Guidelines
|
||||||
|
|
||||||
|
Welcome to the PortaPack Mayhem repository. This document sets mandatory rules and best practices for human developers and AI coding assistants. It exists to prevent unverified, untested code from overwhelming the maintainers, while still enabling AI to accelerate development.
|
||||||
|
|
||||||
|
## 🚫 STRICTLY PROHIBITED
|
||||||
|
|
||||||
|
1. **No Fully Automated Pull Requests**
|
||||||
|
Autonomous agents (bots, crawlers, automated workflows) must **never** open a Pull Request without direct human supervision, review, and explicit approval.
|
||||||
|
|
||||||
|
2. **No “Blind” AI-Generated Submissions**
|
||||||
|
If you cannot explain, debug, or maintain the C++/DSP code an AI produced for you, do **not** submit it. PRs that appear to be untested, AI-only “compile-and-pray” attempts will be closed immediately.
|
||||||
|
|
||||||
|
3. **No Unverified Hardware Interactions**
|
||||||
|
AI cannot test on real hardware. Do **not** submit code that has not been compiled and physically tested on a PortaPack device (or a highly accurate emulator). This includes UI rendering, RF transmission quality, and baseband stability.
|
||||||
|
|
||||||
|
## ✅ ENCOURAGED AI USE CASES
|
||||||
|
|
||||||
|
We strongly encourage using AI assistants (Copilot, ChatGPT, Claude, local LLMs) as pair-programmers. Excellent use cases include:
|
||||||
|
|
||||||
|
- **Bug Triage & Root-Causing:** Analyze crash logs, stack traces, and complex memory issues to identify the root cause of an open issue.
|
||||||
|
- **Code Comprehension:** Map Mayhem’s architecture, understand M0‑M4 IPC, or break down complex DSP algorithms.
|
||||||
|
- **Development Assistance:** Generate boilerplate UI code, write unit tests, optimize math functions, or draft documentation – provided a human reviews and refines the output.
|
||||||
|
- **Targeted Refactoring:** Suggest cleaner, modern C++ for small legacy code segments. **Do not attempt to rewrite the entire codebase.**
|
||||||
|
|
||||||
|
## 📋 MANDATORY RULES FOR AI‑ASSISTED CONTRIBUTIONS
|
||||||
|
|
||||||
|
When submitting AI‑assisted work, you must:
|
||||||
|
|
||||||
|
1. **Take Full Ownership**
|
||||||
|
You are strictly responsible for every line of code you submit, regardless of origin. Be prepared to answer detailed technical questions during review.
|
||||||
|
|
||||||
|
2. **Hardware Verification Required**
|
||||||
|
In your PR, explicitly state: *“I have compiled this code and tested it on physical PortaPack hardware.”* PRs without this statement will be rejected.
|
||||||
|
|
||||||
|
3. **Transparency (Recommended)**
|
||||||
|
If AI significantly helped architect a feature or fix a complex bug, mention it briefly in the PR description (e.g., *“Used an LLM to help optimize the DSP filter loop”*).
|
||||||
|
|
||||||
|
4. **Adhere to Coding Standards**
|
||||||
|
Ensure all code matches the existing Mayhem style and formatting before opening the PR.
|
||||||
|
|
||||||
|
## ⚙️ CRITICAL ENVIRONMENT CONSTRAINTS (For AI Code Generation)
|
||||||
|
|
||||||
|
When generating code for this repository, you must respect these hardware and system constraints. **Violating any of them will result in immediate rejection.**
|
||||||
|
|
||||||
|
- **Severe Memory Limits** – RAM is extremely scarce. Every byte counts. Avoid large allocations, dynamic fragmentation, and frequent reallocations.
|
||||||
|
- **Platform** – ChibiOS 2.6 on NXP LPC4320(HackRF One) / LPC4330 (HackRF Pro AKA PRALINE). M0 core handles UI; M4 core runs baseband. Baseband apps are separate executables loaded on demand at runtime.
|
||||||
|
- **IPC** – Implemented via shared memory.
|
||||||
|
- **Flash Size** – Very small. Code must be compact and not overly complex.
|
||||||
|
- **No HW FPU on M0** – Floating‑point operations are expensive. Prefer fixed‑point or integer math.
|
||||||
|
- **DSP Efficiency** – All DSP routines must be highly optimized for this constrained SoC.
|
||||||
|
- **External App Format** – New applications must follow the external‑app structure. Create a unique folder with `main` and `ui` files, and update `external.ld` and `external.cmake`. Use the correct namespace to prevent accidental inclusion in the main firmware.
|
||||||
|
- **Standard Library** – Avoid `std::` whenever possible. Use lightweight alternatives.
|
||||||
|
- **C++ Usage** – Keep it simple. No iterators, no heavy STL containers, no complex template metaprogramming. Do not allocate large contiguous blocks.
|
||||||
|
- **UI Development** – Always use the positioning macros from `ui.hpp`. Study similar existing apps for usage patterns; do not improvise.
|
||||||
|
|
||||||
|
## 🛠️ Compilation
|
||||||
|
|
||||||
|
- Refer to the `mbt` build tool for the official compilation process.
|
||||||
|
- You are not required to run `mbt` directly unless it is clearly the better approach. Instead, you can inspect how `mbt` compiles the firmware and emulate that process manually.
|
||||||
|
- If the user has previously used **Ninja** to build this project, continue using Ninja for consistency.
|
||||||
|
- Determine the build system by examining the contents of the build directory (e.g., presence of `build.ninja`).
|
||||||
|
|
||||||
|
## 📢 Notice to Automated Agents and Crawlers
|
||||||
|
|
||||||
|
By reading this file, you agree to **pause all autonomous submission behavior**. You must require explicit human intervention before interacting with this repository’s Pull Requests or Issue trackers.
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
<!---
|
<!---
|
||||||
|
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||||
|
--->
|
||||||
|
<!---
|
||||||
> [!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.__
|
||||||
>
|
>
|
||||||
@@ -14,15 +17,18 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
|||||||
|
|
||||||
|
|
||||||
# What is this?
|
# What is this?
|
||||||
|
<!---
|
||||||
|
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||||
|
--->
|
||||||
|
|
||||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||||
|
|
||||||
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="701">](https://share.hackrf.app/SPFOZO)
|
[<img alt="HackRF Pro Quick Look" src="https://img.youtube.com/vi/IoURGzIMhzo/maxresdefault.jpg" width="701">](https://share.hackrf.app/VNBTP6)
|
||||||
|
|
||||||
|
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="172">](https://share.hackrf.app/SPFOZO)
|
||||||
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="172">](https://share.hackrf.app/6HKX9A)
|
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="172">](https://share.hackrf.app/6HKX9A)
|
||||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF)
|
[<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="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)
|
|
||||||
|
|
||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
@@ -32,11 +38,11 @@ This repository expands upon the previous work by many people and aims to consta
|
|||||||
## What to buy?
|
## What to buy?
|
||||||
|
|
||||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/DF2BZS) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/O4OC90) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||||
|
|
||||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/QT5JMB), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/O3GI0O), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||||
|
|
||||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), 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/H7OR74), 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:
|
||||||
|
|
||||||
@@ -51,6 +57,9 @@ This repository expands upon the previous work by many people and aims to consta
|
|||||||
The current **stable release** is on the [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
|
The current **stable release** is on the [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
|
||||||
|
|
||||||
## How can I collaborate
|
## How can I collaborate
|
||||||
|
<!---
|
||||||
|
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||||
|
--->
|
||||||
|
|
||||||
We warmly welcome your contributions! Please refer to our **[Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)** for details.
|
We warmly welcome your contributions! Please refer to our **[Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)** for details.
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ add_subdirectory(baseband)
|
|||||||
add_subdirectory(standalone)
|
add_subdirectory(standalone)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
|
add_dependencies(application.elf libopencm3_hackrf_usb)
|
||||||
|
|
||||||
if(BOARD STREQUAL "PRALINE")
|
if(BOARD STREQUAL "PRALINE")
|
||||||
math(EXPR PRALINE_FINAL_SIZE "4 * 1024 * 1024")
|
math(EXPR PRALINE_FINAL_SIZE "4 * 1024 * 1024")
|
||||||
set(PRALINE_FPGA_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../hackrf/firmware/fpga/build/praline_fpga.bin)
|
set(PRALINE_FPGA_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../hackrf/firmware/fpga/build/praline_fpga.bin)
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ set(CPPSRC
|
|||||||
${COMMON}/battery.cpp
|
${COMMON}/battery.cpp
|
||||||
${COMMON}/performance_counter.cpp
|
${COMMON}/performance_counter.cpp
|
||||||
${COMMON}/bmpfile.cpp
|
${COMMON}/bmpfile.cpp
|
||||||
|
${COMMON}/gpio.cpp
|
||||||
app_settings.cpp
|
app_settings.cpp
|
||||||
audio.cpp
|
audio.cpp
|
||||||
baseband_api.cpp
|
baseband_api.cpp
|
||||||
@@ -238,7 +239,6 @@ set(CPPSRC
|
|||||||
serializer.cpp
|
serializer.cpp
|
||||||
spectrum_color_lut.cpp
|
spectrum_color_lut.cpp
|
||||||
string_format.cpp
|
string_format.cpp
|
||||||
temperature_logger.cpp
|
|
||||||
theme.cpp
|
theme.cpp
|
||||||
touch.cpp
|
touch.cpp
|
||||||
tone_key.cpp
|
tone_key.cpp
|
||||||
@@ -279,16 +279,12 @@ 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/ais_app.cpp
|
|
||||||
apps/analog_audio_app.cpp
|
apps/analog_audio_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/pocsag_app.cpp
|
apps/pocsag_app.cpp
|
||||||
apps/ui_about_simple.cpp
|
apps/ui_about_simple.cpp
|
||||||
apps/ui_adsb_rx.cpp
|
|
||||||
apps/ui_aprs_rx.cpp
|
|
||||||
apps/ui_aprs_tx.cpp
|
|
||||||
apps/ui_battinfo.cpp
|
apps/ui_battinfo.cpp
|
||||||
apps/ui_bmp_file_viewer.cpp
|
apps/ui_bmp_file_viewer.cpp
|
||||||
apps/ui_btle_rx.cpp
|
apps/ui_btle_rx.cpp
|
||||||
@@ -332,6 +328,7 @@ set(CPPSRC
|
|||||||
${CPLD_H4M_DATA_CPP}
|
${CPLD_H4M_DATA_CPP}
|
||||||
${HACKRF_CPLD_DATA_CPP}
|
${HACKRF_CPLD_DATA_CPP}
|
||||||
ui_external_items_menu_loader.cpp
|
ui_external_items_menu_loader.cpp
|
||||||
|
view_factory_base.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||||
@@ -492,7 +489,10 @@ add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
|
|||||||
target_link_options(${PROJECT_NAME}.elf PRIVATE
|
target_link_options(${PROJECT_NAME}.elf PRIVATE
|
||||||
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
|
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
|
||||||
)
|
)
|
||||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
# NB: LPC43xx_M0.ld does INCLUDE external.ld, so that one has to be listed too -
|
||||||
|
# otherwise editing the external app section rules silently does not relink.
|
||||||
|
set_target_properties(${PROJECT_NAME}.elf PROPERTIES
|
||||||
|
LINK_DEPENDS "${LDSCRIPT};${CMAKE_CURRENT_SOURCE_DIR}/external/external.ld")
|
||||||
add_definitions(${DEFS})
|
add_definitions(${DEFS})
|
||||||
include_directories(. ${INCDIR})
|
include_directories(. ${INCDIR})
|
||||||
link_directories(${LLIBDIR})
|
link_directories(${LLIBDIR})
|
||||||
@@ -511,3 +511,5 @@ add_custom_target(
|
|||||||
${PROJECT_NAME}
|
${PROJECT_NAME}
|
||||||
DEPENDS ${PROJECT_NAME}.bin
|
DEPENDS ${PROJECT_NAME}.bin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_dependencies(${PROJECT_NAME} baseband)
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
|
|||||||
case SettingType::I64:
|
case SettingType::I64:
|
||||||
parse_int(value, as<int64_t>());
|
parse_int(value, as<int64_t>());
|
||||||
break;
|
break;
|
||||||
|
case SettingType::U64:
|
||||||
|
parse_int(value, as<uint64_t>());
|
||||||
|
break;
|
||||||
case SettingType::I32:
|
case SettingType::I32:
|
||||||
parse_int(value, as<int32_t>());
|
parse_int(value, as<int32_t>());
|
||||||
break;
|
break;
|
||||||
@@ -87,6 +90,9 @@ void BoundSetting::write(File& file) const {
|
|||||||
case SettingType::I64:
|
case SettingType::I64:
|
||||||
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
||||||
break;
|
break;
|
||||||
|
case SettingType::U64:
|
||||||
|
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
|
||||||
|
break;
|
||||||
case SettingType::I32:
|
case SettingType::I32:
|
||||||
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
||||||
break;
|
break;
|
||||||
@@ -97,9 +103,8 @@ void BoundSetting::write(File& file) const {
|
|||||||
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
||||||
break;
|
break;
|
||||||
case SettingType::String: {
|
case SettingType::String: {
|
||||||
const auto& str = as<std::string>();
|
file.write_line(as<std::string>());
|
||||||
file.write(str.data(), str.length());
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case SettingType::Bool:
|
case SettingType::Bool:
|
||||||
file.write(as<bool>() ? "1" : "0", 1);
|
file.write(as<bool>() ? "1" : "0", 1);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
|
#include "hackrf_hal.hpp"
|
||||||
#include "max283x.hpp"
|
#include "max283x.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ class BoundSetting {
|
|||||||
/* The type of bound setting. */
|
/* The type of bound setting. */
|
||||||
enum class SettingType : uint8_t {
|
enum class SettingType : uint8_t {
|
||||||
I64,
|
I64,
|
||||||
|
U64,
|
||||||
I32,
|
I32,
|
||||||
U32,
|
U32,
|
||||||
U8,
|
U8,
|
||||||
@@ -59,6 +61,9 @@ class BoundSetting {
|
|||||||
BoundSetting(std::string_view name, int64_t* target)
|
BoundSetting(std::string_view name, int64_t* target)
|
||||||
: name_{name}, target_{target}, type_{SettingType::I64} {}
|
: name_{name}, target_{target}, type_{SettingType::I64} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, uint64_t* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::U64} {}
|
||||||
|
|
||||||
BoundSetting(std::string_view name, int32_t* target)
|
BoundSetting(std::string_view name, int32_t* target)
|
||||||
: name_{name}, target_{target}, type_{SettingType::I32} {}
|
: name_{name}, target_{target}, type_{SettingType::I32} {}
|
||||||
|
|
||||||
@@ -134,7 +139,7 @@ struct AppSettings {
|
|||||||
uint8_t vga = 32;
|
uint8_t vga = 32;
|
||||||
uint8_t rx_amp = 0;
|
uint8_t rx_amp = 0;
|
||||||
uint8_t tx_amp = 0;
|
uint8_t tx_amp = 0;
|
||||||
uint8_t tx_gain = 35;
|
uint8_t tx_gain = hackrf::one::default_tx_gain_db;
|
||||||
uint32_t channel_bandwidth = 1;
|
uint32_t channel_bandwidth = 1;
|
||||||
uint32_t rx_frequency;
|
uint32_t rx_frequency;
|
||||||
uint32_t tx_frequency;
|
uint32_t tx_frequency;
|
||||||
|
|||||||
@@ -280,6 +280,12 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
field_frequency.on_show_options = [this]() {
|
field_frequency.on_show_options = [this]() {
|
||||||
this->on_show_options_frequency();
|
this->on_show_options_frequency();
|
||||||
};
|
};
|
||||||
|
field_frequency.changing = [this](rf::Frequency frequency) {
|
||||||
|
return this->on_frequency_changed(frequency);
|
||||||
|
};
|
||||||
|
field_frequency.entered = [this](rf::Frequency frequency) {
|
||||||
|
this->set_frequency_absolute(frequency);
|
||||||
|
};
|
||||||
|
|
||||||
field_lna.on_show_options = [this]() {
|
field_lna.on_show_options = [this]() {
|
||||||
this->on_show_options_rf_gain();
|
this->on_show_options_rf_gain();
|
||||||
@@ -308,8 +314,10 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
};
|
};
|
||||||
|
|
||||||
waterfall.on_select = [this](int32_t offset) {
|
waterfall.on_select = [this](int32_t offset) {
|
||||||
field_frequency.set_value(receiver_model.target_frequency() + offset);
|
field_frequency.set_value(
|
||||||
|
field_frequency.value() + offset * receiver_model.frequency_step());
|
||||||
};
|
};
|
||||||
|
waterfall.set_live_tuning(true);
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
button_pro.on_select = [this](Button&) { this->on_show_options_praline(); };
|
button_pro.on_select = [this](Button&) { this->on_show_options_praline(); };
|
||||||
@@ -319,6 +327,7 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
|
|
||||||
// This call starts the correct baseband image to run
|
// This call starts the correct baseband image to run
|
||||||
// and sets the radio up as necessary for the given modulation.
|
// and sets the radio up as necessary for the given modulation.
|
||||||
|
sliding_center_frequency = receiver_model.target_frequency();
|
||||||
on_modulation_changed(modulation);
|
on_modulation_changed(modulation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +337,7 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
: AnalogAudioView(nav) {
|
: AnalogAudioView(nav) {
|
||||||
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
||||||
// TODO: Which other settings make sense to override?
|
// TODO: Which other settings make sense to override?
|
||||||
field_frequency.set_value(override.frequency_app_override);
|
set_frequency_absolute(override.frequency_app_override);
|
||||||
on_frequency_step_changed(override.frequency_step);
|
on_frequency_step_changed(override.frequency_step);
|
||||||
options_modulation.set_by_value(toUType(override.mode));
|
options_modulation.set_by_value(toUType(override.mode));
|
||||||
}
|
}
|
||||||
@@ -573,10 +582,14 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
|||||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
||||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
|
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
|
||||||
|
|
||||||
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
|
reset_sliding_frequency(modulation);
|
||||||
|
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
|
|
||||||
|
if (sliding_enabled) {
|
||||||
|
baseband::set_audio_ddc_frequency(0);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This doesn't belong here! There's a better way.
|
// TODO: This doesn't belong here! There's a better way.
|
||||||
size_t sampling_rate = 0;
|
size_t sampling_rate = 0;
|
||||||
switch (modulation) {
|
switch (modulation) {
|
||||||
@@ -610,7 +623,64 @@ void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AnalogAudioView::on_freqchg(int64_t freq) {
|
void AnalogAudioView::on_freqchg(int64_t freq) {
|
||||||
field_frequency.set_value(freq);
|
set_frequency_absolute(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnalogAudioView::set_frequency_absolute(rf::Frequency frequency) {
|
||||||
|
if (!sliding_enabled) {
|
||||||
|
field_frequency.set_value(frequency);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliding_center_frequency = frequency;
|
||||||
|
|
||||||
|
/* set_value() does not call on_change when the displayed frequency is
|
||||||
|
* already equal, so reset the hardware and DDC explicitly in that case. */
|
||||||
|
if (field_frequency.value() == frequency) {
|
||||||
|
receiver_model.set_target_frequency_with_hidden_offset(frequency, 0);
|
||||||
|
baseband::set_audio_ddc_frequency(0);
|
||||||
|
} else {
|
||||||
|
field_frequency.set_value(frequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t AnalogAudioView::sliding_limit() const {
|
||||||
|
const bool zoom_x2 =
|
||||||
|
receiver_model.modulation() == ReceiverModel::Mode::AMAudio &&
|
||||||
|
previous_zoom != 0;
|
||||||
|
return zoom_x2 ? sliding_limit_zoom_x2 : sliding_limit_zoom_x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnalogAudioView::reset_sliding_frequency(ReceiverModel::Mode modulation) {
|
||||||
|
sliding_enabled =
|
||||||
|
modulation == ReceiverModel::Mode::AMAudio ||
|
||||||
|
modulation == ReceiverModel::Mode::NarrowbandFMAudio;
|
||||||
|
sliding_center_frequency = receiver_model.target_frequency();
|
||||||
|
|
||||||
|
/* AMFM keeps its existing Wefax offset; sliding applies to AM and NFM. */
|
||||||
|
receiver_model.set_hidden_offset(
|
||||||
|
modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalogAudioView::on_frequency_changed(rf::Frequency frequency) {
|
||||||
|
if (!sliding_enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto limit = sliding_limit();
|
||||||
|
int64_t offset = frequency - sliding_center_frequency;
|
||||||
|
if (offset > limit) {
|
||||||
|
sliding_center_frequency = frequency - limit;
|
||||||
|
offset = limit;
|
||||||
|
} else if (offset < -limit) {
|
||||||
|
sliding_center_frequency = frequency + limit;
|
||||||
|
offset = -limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store the displayed frequency and retune the hardware centre atomically. */
|
||||||
|
receiver_model.set_target_frequency_with_hidden_offset(
|
||||||
|
frequency, sliding_center_frequency - frequency);
|
||||||
|
baseband::set_audio_ddc_frequency(static_cast<int32_t>(offset));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
|
|||||||
@@ -269,6 +269,10 @@ class AnalogAudioView : public View {
|
|||||||
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
|
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
|
||||||
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
|
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
|
||||||
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
|
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
|
||||||
|
static constexpr int32_t sliding_limit_zoom_x1 = 50000;
|
||||||
|
static constexpr int32_t sliding_limit_zoom_x2 = 30000;
|
||||||
|
rf::Frequency sliding_center_frequency{0};
|
||||||
|
bool sliding_enabled{false};
|
||||||
|
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_audio",
|
"rx_audio",
|
||||||
@@ -354,6 +358,10 @@ class AnalogAudioView : public View {
|
|||||||
void handle_coded_squelch(uint32_t value);
|
void handle_coded_squelch(uint32_t value);
|
||||||
|
|
||||||
void on_freqchg(int64_t freq);
|
void on_freqchg(int64_t freq);
|
||||||
|
bool on_frequency_changed(rf::Frequency frequency);
|
||||||
|
void set_frequency_absolute(rf::Frequency frequency);
|
||||||
|
int32_t sliding_limit() const;
|
||||||
|
void reset_sliding_frequency(ReceiverModel::Mode modulation);
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_coded_squelch{
|
MessageHandlerRegistration message_handler_coded_squelch{
|
||||||
Message::ID::CodedSquelch,
|
Message::ID::CodedSquelch,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
|
|||||||
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
|
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
freq_hz = UINT64_MAX;
|
freq_hz = 2'402'000'000ull; // Default to channel 37 frequency if invalid channel number
|
||||||
}
|
}
|
||||||
|
|
||||||
return freq_hz;
|
return freq_hz;
|
||||||
@@ -254,7 +254,7 @@ void BLETxView::send_packet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BLETxView::start() {
|
void BLETxView::start() {
|
||||||
if (file_path.empty()) {
|
if (file_path.empty() || num_packets == 0) {
|
||||||
file_error();
|
file_error();
|
||||||
check_loop.set_value(false);
|
check_loop.set_value(false);
|
||||||
return;
|
return;
|
||||||
@@ -266,6 +266,8 @@ void BLETxView::start() {
|
|||||||
button_play.set_bitmap(&bitmap_stop);
|
button_play.set_bitmap(&bitmap_stop);
|
||||||
is_running = true;
|
is_running = true;
|
||||||
|
|
||||||
|
advCount = 0;
|
||||||
|
|
||||||
send_packet();
|
send_packet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,55 +298,61 @@ void BLETxView::on_timer() {
|
|||||||
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
|
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
|
||||||
// TODO: make use of progress variable
|
// TODO: make use of progress variable
|
||||||
(void)progress;
|
(void)progress;
|
||||||
|
if (!is_active() || num_packets == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
if (is_active()) {
|
if (is_active()) {
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
|
|
||||||
if (auto_channel) {
|
if (auto_channel) {
|
||||||
switch (advCount) {
|
advCount++;
|
||||||
case 0:
|
|
||||||
channel_number = 37;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
channel_number = 38;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channel_number = 39;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
|
||||||
|
|
||||||
if (advCount == 3) {
|
if (advCount == 3) {
|
||||||
channel_number = 37;
|
channel_number = 37;
|
||||||
packet_counter--;
|
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||||
|
if (packet_counter > 0) packet_counter--;
|
||||||
packetDone = true;
|
packetDone = true;
|
||||||
advCount = 0;
|
advCount = 0;
|
||||||
} else {
|
} else {
|
||||||
|
channel_number = 37 + advCount;
|
||||||
|
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||||
|
send_packet();
|
||||||
|
}
|
||||||
|
} else if (all_channels) {
|
||||||
|
advCount++;
|
||||||
|
if (advCount == 40) {
|
||||||
|
channel_number = 0;
|
||||||
|
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||||
|
if (packet_counter > 0) packet_counter--;
|
||||||
|
packetDone = true;
|
||||||
|
advCount = 0;
|
||||||
|
} else {
|
||||||
|
channel_number = advCount;
|
||||||
|
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||||
send_packet();
|
send_packet();
|
||||||
advCount++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packet_counter--;
|
if (packet_counter > 0) packet_counter--;
|
||||||
packetDone = true;
|
packetDone = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reached end of current packet repeats.
|
// Reached end of current packet repeats.
|
||||||
if (packet_counter == 0) {
|
if (packet_counter == 0) {
|
||||||
// Done sending all packets.
|
// Done sending all packets.
|
||||||
if (current_packet == (num_packets - 1)) {
|
current_packet++;
|
||||||
current_packet = 0;
|
if (current_packet >= num_packets) {
|
||||||
|
current_packet = 0;
|
||||||
|
|
||||||
// If looping, restart from beginning.
|
// If looping, restart from beginning.
|
||||||
if (check_loop.value()) {
|
if (check_loop.value()) {
|
||||||
update_current_packet(packets[current_packet], current_packet);
|
|
||||||
} else {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_packet++;
|
|
||||||
update_current_packet(packets[current_packet], current_packet);
|
update_current_packet(packets[current_packet], current_packet);
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
update_current_packet(packets[current_packet], current_packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,16 +393,20 @@ BLETxView::BLETxView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
options_channel.on_change = [this](size_t, int32_t i) {
|
options_channel.on_change = [this](size_t, int32_t i) {
|
||||||
// If we selected Auto don't do anything and Auto will handle changing.
|
auto_channel = (i == BLT_CHAN_AUTO);
|
||||||
if (i == 40) {
|
all_channels = (i == BLT_CHAN_ALL);
|
||||||
auto_channel = true;
|
|
||||||
return;
|
advCount = 0;
|
||||||
|
|
||||||
|
if (auto_channel) {
|
||||||
|
channel_number = 37;
|
||||||
|
} else if (all_channels) {
|
||||||
|
channel_number = 0;
|
||||||
} else {
|
} else {
|
||||||
auto_channel = false;
|
channel_number = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||||
channel_number = i;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options_speed.on_change = [this](size_t, int32_t i) {
|
options_speed.on_change = [this](size_t, int32_t i) {
|
||||||
@@ -403,7 +415,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
options_speed.set_selected_index(0);
|
options_speed.set_selected_index(0);
|
||||||
options_channel.set_selected_index(3);
|
options_channel.set_by_value(BLT_CHAN_AUTO);
|
||||||
|
|
||||||
check_rand_mac.set_value(false);
|
check_rand_mac.set_value(false);
|
||||||
check_rand_mac.on_select = [this](Checkbox&, bool v) {
|
check_rand_mac.on_select = [this](Checkbox&, bool v) {
|
||||||
@@ -413,8 +425,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
|||||||
button_open.on_select = [this](Button&) {
|
button_open.on_select = [this](Button&) {
|
||||||
auto open_view = nav_.push<FileLoadView>(".TXT");
|
auto open_view = nav_.push<FileLoadView>(".TXT");
|
||||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||||
on_file_changed(new_file_path);
|
this->on_file_changed(new_file_path);
|
||||||
|
|
||||||
nav_.set_on_pop([this]() { button_play.focus(); });
|
nav_.set_on_pop([this]() { button_play.focus(); });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -427,7 +438,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
|||||||
64,
|
64,
|
||||||
ENTER_KEYBOARD_MODE_ALPHA,
|
ENTER_KEYBOARD_MODE_ALPHA,
|
||||||
[this](std::string& buffer) {
|
[this](std::string& buffer) {
|
||||||
on_save_file(buffer);
|
this->on_save_file(buffer);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -506,10 +517,10 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
|
readUntil(data_file, packets[num_packets].macAddress, sizeof(packets[num_packets].macAddress), ' ');
|
||||||
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
|
readUntil(data_file, packets[num_packets].advertisementData, sizeof(packets[num_packets].advertisementData), ' ');
|
||||||
readUntil(data_file, packets[num_packets].packetType, max_packet_type_str, ' ');
|
readUntil(data_file, packets[num_packets].packetType, sizeof(packets[num_packets].packetType), ' ');
|
||||||
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
|
readUntil(data_file, packets[num_packets].packetCount, sizeof(packets[num_packets].packetCount), '\n');
|
||||||
|
|
||||||
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
|
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
|
||||||
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
|
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
|
||||||
@@ -542,10 +553,8 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
|||||||
num_packets++;
|
num_packets++;
|
||||||
|
|
||||||
} while (num_packets < max_num_packets);
|
} while (num_packets < max_num_packets);
|
||||||
|
|
||||||
update_current_packet(packets[0], 0);
|
|
||||||
|
|
||||||
data_file.close();
|
data_file.close();
|
||||||
|
update_current_packet(packets[0], 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +579,9 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
|||||||
|
|
||||||
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
|
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
|
||||||
|
|
||||||
|
packet_counter = packet.packet_count;
|
||||||
|
current_packet = currentIndex;
|
||||||
|
|
||||||
text_packet_index.set(to_string_dec_uint(current_packet));
|
text_packet_index.set(to_string_dec_uint(current_packet));
|
||||||
|
|
||||||
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
|
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
|
||||||
@@ -578,9 +590,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
|||||||
|
|
||||||
progressbar.set_max(packet.packet_count);
|
progressbar.set_max(packet.packet_count);
|
||||||
|
|
||||||
packet_counter = packet.packet_count;
|
|
||||||
current_packet = currentIndex;
|
|
||||||
|
|
||||||
dataFile.create(dataTempFilePath);
|
dataFile.create(dataTempFilePath);
|
||||||
|
|
||||||
for (const std::string& str : strings) {
|
for (const std::string& str : strings) {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#define BLT_CHAN_AUTO 40
|
||||||
|
#define BLT_CHAN_ALL 41
|
||||||
|
|
||||||
class BLELoggerTx {
|
class BLELoggerTx {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
@@ -94,7 +97,7 @@ struct BLETxPacket {
|
|||||||
char advertisementData[63];
|
char advertisementData[63];
|
||||||
char packetCount[11];
|
char packetCount[11];
|
||||||
char packetType[17];
|
char packetType[17];
|
||||||
uint32_t packet_count;
|
uint32_t packet_count; // repeat the same packet count times
|
||||||
PKT_TYPE pduType;
|
PKT_TYPE pduType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -145,6 +148,7 @@ class BLETxView : public View {
|
|||||||
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
|
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
|
||||||
uint8_t channel_number = 37;
|
uint8_t channel_number = 37;
|
||||||
bool auto_channel = false;
|
bool auto_channel = false;
|
||||||
|
bool all_channels = false;
|
||||||
|
|
||||||
char randomMac[13] = "010203040506";
|
char randomMac[13] = "010203040506";
|
||||||
|
|
||||||
@@ -156,10 +160,10 @@ class BLETxView : public View {
|
|||||||
int16_t auto_channel_period{6};
|
int16_t auto_channel_period{6};
|
||||||
|
|
||||||
bool repeatLoop = false;
|
bool repeatLoop = false;
|
||||||
uint32_t packet_counter{0};
|
uint32_t packet_counter{0}; // the counter (decr) of the actual packet as it can be sent multiple times
|
||||||
uint32_t num_packets{0};
|
uint32_t num_packets{0}; // how many packets in the file
|
||||||
uint32_t current_packet{0};
|
uint32_t current_packet{0}; // the actual packet's num, i'm sending if i have multiple packets
|
||||||
uint8_t packetTxCount{0};
|
uint8_t packetTxCount{0}; // not used
|
||||||
bool packetDone = false;
|
bool packetDone = false;
|
||||||
bool random_mac = false;
|
bool random_mac = false;
|
||||||
bool file_override = false;
|
bool file_override = false;
|
||||||
@@ -241,10 +245,48 @@ class BLETxView : public View {
|
|||||||
OptionsField options_channel{
|
OptionsField options_channel{
|
||||||
{11 * 8, 6 * 8},
|
{11 * 8, 6 * 8},
|
||||||
5,
|
5,
|
||||||
{{"Ch.37 ", 37},
|
{{"Ch.0", 0},
|
||||||
|
{"Ch.1", 1},
|
||||||
|
{"Ch.2", 2},
|
||||||
|
{"Ch.3", 3},
|
||||||
|
{"Ch.4", 4},
|
||||||
|
{"Ch.5", 5},
|
||||||
|
{"Ch.6", 6},
|
||||||
|
{"Ch.7", 7},
|
||||||
|
{"Ch.8", 8},
|
||||||
|
{"Ch.9", 9},
|
||||||
|
{"Ch.10", 10},
|
||||||
|
{"Ch.11", 11},
|
||||||
|
{"Ch.12", 12},
|
||||||
|
{"Ch.13", 13},
|
||||||
|
{"Ch.14", 14},
|
||||||
|
{"Ch.15", 15},
|
||||||
|
{"Ch.16", 16},
|
||||||
|
{"Ch.17", 17},
|
||||||
|
{"Ch.18", 18},
|
||||||
|
{"Ch.19", 19},
|
||||||
|
{"Ch.20", 20},
|
||||||
|
{"Ch.21", 21},
|
||||||
|
{"Ch.22", 22},
|
||||||
|
{"Ch.23", 23},
|
||||||
|
{"Ch.24", 24},
|
||||||
|
{"Ch.25", 25},
|
||||||
|
{"Ch.26", 26},
|
||||||
|
{"Ch.27", 27},
|
||||||
|
{"Ch.28", 28},
|
||||||
|
{"Ch.29", 29},
|
||||||
|
{"Ch.30", 30},
|
||||||
|
{"Ch.31", 31},
|
||||||
|
{"Ch.32", 32},
|
||||||
|
{"Ch.33", 33},
|
||||||
|
{"Ch.34", 34},
|
||||||
|
{"Ch.35", 35},
|
||||||
|
{"Ch.36", 36},
|
||||||
|
{"Ch.37", 37},
|
||||||
{"Ch.38", 38},
|
{"Ch.38", 38},
|
||||||
{"Ch.39", 39},
|
{"Ch.39", 39},
|
||||||
{"Auto", 40}}};
|
{"Auto", BLT_CHAN_AUTO},
|
||||||
|
{"All", BLT_CHAN_ALL}}};
|
||||||
|
|
||||||
OptionsField options_adv_type{
|
OptionsField options_adv_type{
|
||||||
{17 * 8, 6 * 8},
|
{17 * 8, 6 * 8},
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ 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_ttef.set("-");
|
text_ttef.set("-");
|
||||||
text_method.set("-");
|
text_method.set("-");
|
||||||
// text_warn.set("");
|
// text_warn.set("");
|
||||||
@@ -60,7 +59,9 @@ void BattinfoView::update_result() {
|
|||||||
}
|
}
|
||||||
bool uichg = false;
|
bool uichg = false;
|
||||||
uint8_t valid_mask = 0;
|
uint8_t valid_mask = 0;
|
||||||
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
|
bool battMayChanged = false;
|
||||||
|
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, battMayChanged);
|
||||||
|
text_state.set((battMayChanged || !persistent_memory::battery_cap_valid()) ? "Needs config." : "ok");
|
||||||
// update text fields
|
// update text fields
|
||||||
if (percent <= 100 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE)
|
if (percent <= 100 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE)
|
||||||
text_percent.set(to_string_dec_uint(percent) + " %");
|
text_percent.set(to_string_dec_uint(percent) + " %");
|
||||||
@@ -76,6 +77,7 @@ void BattinfoView::update_result() {
|
|||||||
labels_opt.hidden(false);
|
labels_opt.hidden(false);
|
||||||
text_current.hidden(false);
|
text_current.hidden(false);
|
||||||
text_charge.hidden(false);
|
text_charge.hidden(false);
|
||||||
|
text_state.hidden(false);
|
||||||
text_current.set(to_string_dec_int(current) + " mA");
|
text_current.set(to_string_dec_int(current) + " mA");
|
||||||
if (current >= 25) // when >25mA it is charging in any scenario
|
if (current >= 25) // when >25mA it is charging in any scenario
|
||||||
text_charge.set("Charging");
|
text_charge.set("Charging");
|
||||||
@@ -98,7 +100,7 @@ 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_state.hidden(true);
|
||||||
text_ttef.hidden(true);
|
text_ttef.hidden(true);
|
||||||
// text_warn.set("");
|
// text_warn.set("");
|
||||||
}
|
}
|
||||||
@@ -143,9 +145,10 @@ void BattinfoView::update_result() {
|
|||||||
} else {
|
} else {
|
||||||
text_method.set("Voltage");
|
text_method.set("Voltage");
|
||||||
}
|
}
|
||||||
|
if (battMayChanged) text_capacity.set_style(Theme::getInstance()->fg_red);
|
||||||
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 >= 25, voltage};
|
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage, battMayChanged};
|
||||||
EventDispatcher::send_message(msg);
|
EventDispatcher::send_message(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +164,7 @@ BattinfoView::BattinfoView(NavigationView& nav)
|
|||||||
&button_settings,
|
&button_settings,
|
||||||
&button_exit,
|
&button_exit,
|
||||||
&text_capacity,
|
&text_capacity,
|
||||||
// &text_cycles,
|
//&text_state, //disabled for now
|
||||||
// &text_warn,
|
// &text_warn,
|
||||||
&text_ttef});
|
&text_ttef});
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class BattinfoView : public View {
|
|||||||
{{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||||
// {{UI_POS_X(2), UI_POS_Y(8)}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
//{{UI_POS_X(2), UI_POS_Y(8)}, "State:", Theme::getInstance()->fg_light->foreground}, //for now, this is disabled. see max ic implementation
|
||||||
{{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,13 +90,14 @@ class BattinfoView : public View {
|
|||||||
{UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
{UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
/* Text text_cycles{
|
Text text_state{
|
||||||
{UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
{UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(13), UI_POS_HEIGHT(1)},
|
||||||
"-"};
|
"ok"};
|
||||||
|
/*
|
||||||
Text text_warn{
|
Text text_warn{
|
||||||
{UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)},
|
{UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)},
|
||||||
""}; */
|
""};
|
||||||
|
*/
|
||||||
|
|
||||||
Button button_settings{
|
Button button_settings{
|
||||||
{UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
|
{UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
|
||||||
|
|||||||
@@ -3176,9 +3176,9 @@ void RFFC5072StatusView::refresh_status() {
|
|||||||
|
|
||||||
// Blink LED
|
// Blink LED
|
||||||
/*for (int i = 0; i < 3; i++) {
|
/*for (int i = 0; i < 3; i++) {
|
||||||
hackrf::one::led_rx.on();
|
hackrf::one::led_rx.setActive();
|
||||||
chThdSleepMilliseconds(100);
|
chThdSleepMilliseconds(100);
|
||||||
hackrf::one::led_rx.off();
|
hackrf::one::led_rx.setInactive();
|
||||||
chThdSleepMilliseconds(100);
|
chThdSleepMilliseconds(100);
|
||||||
}*/
|
}*/
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ DfuMenu2::DfuMenu2(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,
|
||||||
&text_info_line_11});
|
&text_info_line_11,
|
||||||
|
&text_info_line_12});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DfuMenu2::paint(Painter& painter) {
|
void DfuMenu2::paint(Painter& painter) {
|
||||||
@@ -118,9 +119,12 @@ void DfuMenu2::paint(Painter& painter) {
|
|||||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
||||||
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
||||||
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||||
|
text_info_line_12.set(std::string("M") + (radio::get_mixer_invert() ? "Y" : "N") +
|
||||||
|
" B" + (radio::get_baseband_invert() ? "Y" : "N") +
|
||||||
|
" IQ" + ((radio::get_mixer_invert() ^ radio::get_baseband_invert()) ? "Y" : "N"));
|
||||||
|
|
||||||
constexpr auto margin = 5;
|
constexpr auto margin = 5;
|
||||||
constexpr auto lines = 11 + 2;
|
constexpr auto lines = 12 + 2;
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class DfuMenu2 : public View {
|
|||||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Theme::getInstance()->fg_darkcyan->foreground},
|
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
|
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
|
{{5 * CHARACTER_WIDTH, 16 * LINE_HEIGHT}, "IQ Inv:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
@@ -109,6 +110,7 @@ class DfuMenu2 : public View {
|
|||||||
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
|
Text text_info_line_12{{(14 + 1) * CHARACTER_WIDTH, 16 * LINE_HEIGHT, (10 - 1) * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -390,6 +390,7 @@ std::string get_stem(std::string t) {
|
|||||||
return t.substr(0, index);
|
return t.substr(0, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_filename(std::string _s) {
|
std::string get_filename(std::string _s) {
|
||||||
const auto index = _s.find_last_of("/");
|
const auto index = _s.find_last_of("/");
|
||||||
if (index == _s.npos) {
|
if (index == _s.npos) {
|
||||||
@@ -398,6 +399,7 @@ std::string get_filename(std::string _s) {
|
|||||||
return _s.substr(index + 1);
|
return _s.substr(index + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileManBaseView::refresh_list() {
|
void FileManBaseView::refresh_list() {
|
||||||
if (on_refresh_widgets)
|
if (on_refresh_widgets)
|
||||||
on_refresh_widgets(false);
|
on_refresh_widgets(false);
|
||||||
@@ -408,8 +410,6 @@ void FileManBaseView::refresh_list() {
|
|||||||
menu_view.clear();
|
menu_view.clear();
|
||||||
|
|
||||||
for (const auto& entry : entry_list) {
|
for (const auto& entry : entry_list) {
|
||||||
auto entry_name = std::string{entry.path.length() <= max_filename_length ? entry.path : entry.path.substr(0, max_filename_length)};
|
|
||||||
|
|
||||||
if (entry.is_directory) {
|
if (entry.is_directory) {
|
||||||
std::string size_str{};
|
std::string size_str{};
|
||||||
if (entry.path == str_next || entry.path == str_back) {
|
if (entry.path == str_next || entry.path == str_back) {
|
||||||
@@ -418,8 +418,10 @@ void FileManBaseView::refresh_list() {
|
|||||||
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||||
|
// Do not use spaces or padding here. The menu will handle the alignment.
|
||||||
menu_view.add_item(
|
menu_view.add_item(
|
||||||
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
|
{entry.path + "\t" + size_str,
|
||||||
Theme::getInstance()->fg_yellow->foreground,
|
Theme::getInstance()->fg_yellow->foreground,
|
||||||
&bitmap_icon_dir,
|
&bitmap_icon_dir,
|
||||||
[this](KeyEvent key) {
|
[this](KeyEvent key) {
|
||||||
@@ -431,8 +433,9 @@ void FileManBaseView::refresh_list() {
|
|||||||
const auto& assoc = get_assoc(get_extension(entry.path));
|
const auto& assoc = get_assoc(get_extension(entry.path));
|
||||||
auto size_str = to_string_file_size(entry.size);
|
auto size_str = to_string_file_size(entry.size);
|
||||||
|
|
||||||
|
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||||
menu_view.add_item(
|
menu_view.add_item(
|
||||||
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
|
{entry.path + "\t" + size_str,
|
||||||
assoc.color,
|
assoc.color,
|
||||||
assoc.icon,
|
assoc.icon,
|
||||||
[this](KeyEvent key) {
|
[this](KeyEvent key) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static const char* hackrf_magic = "HACKRFFW";
|
|||||||
#define FIRST_CHECKSUM_NIGHTLY 240125
|
#define FIRST_CHECKSUM_NIGHTLY 240125
|
||||||
|
|
||||||
Thread* FlashUtilityView::thread{nullptr};
|
Thread* FlashUtilityView::thread{nullptr};
|
||||||
static constexpr size_t max_filename_length = 26;
|
static constexpr size_t max_filename_length = 60; // max length of filename
|
||||||
|
|
||||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||||
File firmware_file;
|
File firmware_file;
|
||||||
@@ -134,12 +134,6 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlashUtilityView::wait_till_loaded() {
|
|
||||||
while (!isLoaded) {
|
|
||||||
chThdSleepMilliseconds(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
||||||
//
|
//
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
@@ -157,6 +151,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
|||||||
|
|
||||||
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||||
ui::Painter painter;
|
ui::Painter painter;
|
||||||
|
menu_view.hidden(true);
|
||||||
if (endsWith(path, u".tar")) {
|
if (endsWith(path, u".tar")) {
|
||||||
// extract, then update
|
// extract, then update
|
||||||
path = extract_tar(u'/' + path, painter).native();
|
path = extract_tar(u'/' + path, painter).native();
|
||||||
@@ -166,13 +161,14 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
|||||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
|
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
|
||||||
painter.draw_string({0, 60}, *Theme::getInstance()->fg_red, "BAD FIRMWARE FILE OR W/R ERR");
|
painter.draw_string({0, 60}, *Theme::getInstance()->fg_red, "BAD FIRMWARE FILE OR W/R ERR");
|
||||||
chThdSleepMilliseconds(5000);
|
chThdSleepMilliseconds(5000);
|
||||||
|
menu_view.hidden(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||||
Theme::getInstance()->bg_darkest->background);
|
Theme::getInstance()->bg_darkest->background);
|
||||||
|
|
||||||
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
|
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15-45 sec.");
|
||||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
|
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
|
||||||
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
|
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
|
||||||
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class FlashUtilityView : public View {
|
|||||||
|
|
||||||
std::string title() const override { return "Flash Utility"; };
|
std::string title() const override { return "Flash Utility"; };
|
||||||
bool flash_firmware(std::filesystem::path::string_type path);
|
bool flash_firmware(std::filesystem::path::string_type path);
|
||||||
void wait_till_loaded();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
@@ -68,14 +67,6 @@ class FlashUtilityView : public View {
|
|||||||
void firmware_selected(std::filesystem::path::string_type path);
|
void firmware_selected(std::filesystem::path::string_type path);
|
||||||
|
|
||||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||||
bool isLoaded = false;
|
|
||||||
uint8_t refreshcnt = 0;
|
|
||||||
MessageHandlerRegistration message_handler_frame_sync{
|
|
||||||
Message::ID::DisplayFrameSync,
|
|
||||||
[this](const Message* const) {
|
|
||||||
refreshcnt++;
|
|
||||||
if (refreshcnt > 5) isLoaded = true;
|
|
||||||
}};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "ui_looking_glass_app.hpp"
|
#include "ui_looking_glass_app.hpp"
|
||||||
#include "convert.hpp"
|
#include "convert.hpp"
|
||||||
#include "file_reader.hpp"
|
#include "file_reader.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "audio.hpp"
|
#include "audio.hpp"
|
||||||
#include "file_path.hpp"
|
#include "file_path.hpp"
|
||||||
@@ -164,11 +165,13 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
|
|||||||
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, gradient.lut[spectrum_data[xpos]]);
|
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, gradient.lut[spectrum_data[xpos]]);
|
||||||
}
|
}
|
||||||
// indicate RSSI min and max power
|
// indicate RSSI min and max power
|
||||||
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
|
if (show_rssi_guides) {
|
||||||
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
|
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
|
||||||
|
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
|
||||||
|
}
|
||||||
if (last_max_freq != max_freq_hold) {
|
if (last_max_freq != max_freq_hold) {
|
||||||
last_max_freq = max_freq_hold;
|
last_max_freq = max_freq_hold;
|
||||||
freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold));
|
freq_stats.set("MAX: " + to_string_short_freq(max_freq_hold));
|
||||||
}
|
}
|
||||||
plot_marker(marker_pixel_index);
|
plot_marker(marker_pixel_index);
|
||||||
} else {
|
} else {
|
||||||
@@ -325,8 +328,8 @@ void GlassView::update_min(int32_t v) {
|
|||||||
min_size = search_span;
|
min_size = search_span;
|
||||||
if (min_size < 1)
|
if (min_size < 1)
|
||||||
min_size = 1;
|
min_size = 1;
|
||||||
if (v > 7200 - min_size) {
|
if (v > LOOKING_GLASS_MAX_FREQ_MHZ - min_size) {
|
||||||
v = 7200 - min_size;
|
v = LOOKING_GLASS_MAX_FREQ_MHZ - min_size;
|
||||||
}
|
}
|
||||||
if (v > (field_frequency_max.value() - min_size))
|
if (v > (field_frequency_max.value() - min_size))
|
||||||
field_frequency_max.set_value(v + min_size, false);
|
field_frequency_max.set_value(v + min_size, false);
|
||||||
@@ -345,6 +348,9 @@ void GlassView::update_max(int32_t v) {
|
|||||||
if (v < min_size) {
|
if (v < min_size) {
|
||||||
v = min_size;
|
v = min_size;
|
||||||
}
|
}
|
||||||
|
if (v > LOOKING_GLASS_MAX_FREQ_MHZ) {
|
||||||
|
v = LOOKING_GLASS_MAX_FREQ_MHZ;
|
||||||
|
}
|
||||||
if (v < (field_frequency_min.value() + min_size))
|
if (v < (field_frequency_min.value() + min_size))
|
||||||
field_frequency_min.set_value(v - min_size, false);
|
field_frequency_min.set_value(v - min_size, false);
|
||||||
if (locked_range)
|
if (locked_range)
|
||||||
@@ -394,7 +400,8 @@ GlassView::GlassView(
|
|||||||
&button_marker_plus,
|
&button_marker_plus,
|
||||||
&button_jump,
|
&button_jump,
|
||||||
&button_rst,
|
&button_rst,
|
||||||
&freq_stats});
|
&button_rssi,
|
||||||
|
&freq_stats, &button_play});
|
||||||
|
|
||||||
load_presets(); // Load available presets from TXT files (or default).
|
load_presets(); // Load available presets from TXT files (or default).
|
||||||
preset_index = clip<uint8_t>(preset_index, 0, presets_db.size());
|
preset_index = clip<uint8_t>(preset_index, 0, presets_db.size());
|
||||||
@@ -448,6 +455,7 @@ GlassView::GlassView(
|
|||||||
freq_stats.hidden(true);
|
freq_stats.hidden(true);
|
||||||
button_jump.hidden(true);
|
button_jump.hidden(true);
|
||||||
button_rst.hidden(true);
|
button_rst.hidden(true);
|
||||||
|
button_rssi.hidden(true);
|
||||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
|
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -458,6 +466,7 @@ GlassView::GlassView(
|
|||||||
freq_stats.hidden(false);
|
freq_stats.hidden(false);
|
||||||
button_jump.hidden(false);
|
button_jump.hidden(false);
|
||||||
button_rst.hidden(false);
|
button_rst.hidden(false);
|
||||||
|
button_rssi.hidden(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2: // PEAK
|
case 2: // PEAK
|
||||||
@@ -468,6 +477,7 @@ GlassView::GlassView(
|
|||||||
freq_stats.hidden(false);
|
freq_stats.hidden(false);
|
||||||
button_jump.hidden(false);
|
button_jump.hidden(false);
|
||||||
button_rst.hidden(false);
|
button_rst.hidden(false);
|
||||||
|
button_rssi.hidden(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,6 +547,31 @@ GlassView::GlassView(
|
|||||||
reset_live_view();
|
reset_live_view();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
button_rssi.on_select = [this](Button& button) {
|
||||||
|
show_rssi_guides = !show_rssi_guides;
|
||||||
|
if (show_rssi_guides) {
|
||||||
|
button.set_style(Theme::getInstance()->fg_green);
|
||||||
|
} else {
|
||||||
|
button.set_style(Theme::getInstance()->bg_darkest);
|
||||||
|
}
|
||||||
|
reset_live_view();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_play.on_select = [this](ImageButton&) {
|
||||||
|
paused = !paused;
|
||||||
|
if (paused) {
|
||||||
|
button_play.set_foreground(Theme::getInstance()->fg_red->foreground);
|
||||||
|
button_play.set_background(Theme::getInstance()->fg_red->background);
|
||||||
|
baseband::spectrum_streaming_stop();
|
||||||
|
button_play.set_bitmap(&bitmap_play);
|
||||||
|
} else {
|
||||||
|
button_play.set_foreground(Theme::getInstance()->fg_green->foreground);
|
||||||
|
button_play.set_background(Theme::getInstance()->fg_green->background);
|
||||||
|
baseband::spectrum_streaming_start();
|
||||||
|
button_play.set_bitmap(&bitmap_stop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||||
|
|
||||||
// trigger:
|
// trigger:
|
||||||
@@ -587,34 +622,39 @@ void GlassView::on_freqchg(int64_t freq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GlassView::load_presets() {
|
void GlassView::load_presets() {
|
||||||
File presets_file;
|
|
||||||
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
|
|
||||||
presets_db.clear();
|
presets_db.clear();
|
||||||
|
|
||||||
// Add the "Manual" entry.
|
// Add the "Manual" entry.
|
||||||
presets_db.push_back({0, 0, "Manual"});
|
presets_db.push_back({0, 0, "Manual"});
|
||||||
|
|
||||||
if (!error) {
|
scan_root_files(looking_glass_dir, u"*.TXT", [this](const std::filesystem::path& path) {
|
||||||
auto reader = FileLineReader(presets_file);
|
if (path.empty() || path.native()[0] == u'.')
|
||||||
for (const auto& line : reader) {
|
return;
|
||||||
if (line.length() == 0 || line[0] == '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto cols = split_string(line, ',');
|
File presets_file;
|
||||||
if (cols.size() != 3)
|
auto error = presets_file.open(looking_glass_dir / path);
|
||||||
continue;
|
if (!error) {
|
||||||
|
auto reader = FileLineReader(presets_file);
|
||||||
|
for (const auto& line : reader) {
|
||||||
|
if (line.length() == 0 || line[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
preset_entry entry{};
|
auto cols = split_string(line, ',');
|
||||||
parse_int(cols[0], entry.min);
|
if (cols.size() != 3)
|
||||||
parse_int(cols[1], entry.max);
|
continue;
|
||||||
entry.label = trimr(cols[2]);
|
|
||||||
|
|
||||||
if (entry.min == 0 || entry.max == 0 || entry.min >= entry.max)
|
preset_entry entry{};
|
||||||
continue; // Invalid line.
|
parse_int(cols[0], entry.min);
|
||||||
|
parse_int(cols[1], entry.max);
|
||||||
|
entry.label = trimr(cols[2]);
|
||||||
|
|
||||||
presets_db.emplace_back(std::move(entry));
|
if (entry.max == 0 || entry.min >= entry.max)
|
||||||
|
continue; // Invalid line.
|
||||||
|
|
||||||
|
presets_db.emplace_back(std::move(entry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
populate_presets();
|
populate_presets();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace ui {
|
|||||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
||||||
#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
|
#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
|
||||||
#define MHZ_DIV 1000000
|
#define MHZ_DIV 1000000
|
||||||
|
#define LOOKING_GLASS_MAX_FREQ_MHZ 7250 // HackRF upper tuning limit (band_high top), in MHz
|
||||||
|
|
||||||
// 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 )
|
||||||
#define LOOKING_GLASS_FASTSCAN 0
|
#define LOOKING_GLASS_FASTSCAN 0
|
||||||
@@ -129,6 +130,8 @@ class GlassView : public View {
|
|||||||
void populate_presets();
|
void populate_presets();
|
||||||
void launch_audio(rf::Frequency center_freq);
|
void launch_audio(rf::Frequency center_freq);
|
||||||
|
|
||||||
|
bool paused{false};
|
||||||
|
|
||||||
rf::Frequency search_span{0};
|
rf::Frequency search_span{0};
|
||||||
rf::Frequency f_center{0};
|
rf::Frequency f_center{0};
|
||||||
rf::Frequency f_center_ini{0};
|
rf::Frequency f_center_ini{0};
|
||||||
@@ -164,43 +167,44 @@ class GlassView : public View {
|
|||||||
uint8_t bin_length = screen_width;
|
uint8_t bin_length = screen_width;
|
||||||
uint8_t offset = 0;
|
uint8_t offset = 0;
|
||||||
uint8_t ignore_dc = 0;
|
uint8_t ignore_dc = 0;
|
||||||
|
bool show_rssi_guides = false;
|
||||||
|
|
||||||
// start of spectrum area
|
// start of spectrum area
|
||||||
static constexpr int spectrum_y = (108 + 16);
|
static constexpr int spectrum_y = (108 + 16);
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0, UI_POS_Y(0)}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
|
{{0, UI_POS_Y(0)}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
|
{{0, UI_POS_Y(1)}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 2 * 16}, "P:", Theme::getInstance()->fg_light->foreground},
|
{{0, UI_POS_Y(2)}, "P:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 3 * 16}, "MARKER( / ): MHz", Theme::getInstance()->fg_light->foreground},
|
{{0, UI_POS_Y(3)}, "MARKER( / ): MHz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 4 * 16}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
|
{{0, UI_POS_Y(4)}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_frequency_min{
|
NumberField field_frequency_min{
|
||||||
{4 * 8, UI_POS_Y(0)},
|
{UI_POS_X(4), UI_POS_Y(0)},
|
||||||
4,
|
4,
|
||||||
{0, 7199},
|
{0, LOOKING_GLASS_MAX_FREQ_MHZ - 1},
|
||||||
1, // number of steps by encoder delta
|
1, // number of steps by encoder delta
|
||||||
' '};
|
' '};
|
||||||
|
|
||||||
NumberField field_frequency_max{
|
NumberField field_frequency_max{
|
||||||
{13 * 8, UI_POS_Y(0)},
|
{UI_POS_X(13), UI_POS_Y(0)},
|
||||||
4,
|
4,
|
||||||
{1, 7200},
|
{1, LOOKING_GLASS_MAX_FREQ_MHZ},
|
||||||
1, // number of steps by encoder delta
|
1, // number of steps by encoder delta
|
||||||
' '};
|
' '};
|
||||||
|
|
||||||
LNAGainField field_lna{
|
LNAGainField field_lna{
|
||||||
{21 * 8, UI_POS_Y(0)}};
|
{UI_POS_X(21), UI_POS_Y(0)}};
|
||||||
|
|
||||||
VGAGainField field_vga{
|
VGAGainField field_vga{
|
||||||
{27 * 8, UI_POS_Y(0)}};
|
{UI_POS_X(27), UI_POS_Y(0)}};
|
||||||
|
|
||||||
TextField field_range{
|
TextField field_range{
|
||||||
{6 * 8, 1 * 16, 6 * 8, 16},
|
{UI_POS_X(6), UI_POS_Y(1), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
OptionsField filter_config{
|
OptionsField filter_config{
|
||||||
{20 * 8, 1 * 16},
|
{UI_POS_X(20), UI_POS_Y(1)},
|
||||||
4,
|
4,
|
||||||
{
|
{
|
||||||
{"OFF ", 0},
|
{"OFF ", 0},
|
||||||
@@ -209,41 +213,47 @@ class GlassView : public View {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
RFAmpField field_rf_amp{
|
RFAmpField field_rf_amp{
|
||||||
{28 * 8, 1 * 16}};
|
{UI_POS_X(28), UI_POS_Y(1)}};
|
||||||
|
|
||||||
OptionsField range_presets{
|
OptionsField range_presets{
|
||||||
{2 * 8, 2 * 16},
|
{UI_POS_X(2), UI_POS_Y(2)},
|
||||||
20,
|
20,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
ButtonWithEncoder button_beep_squelch{
|
ButtonWithEncoder button_beep_squelch{
|
||||||
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
|
{UI_POS_X_RIGHT(8), UI_POS_Y(2.25), UI_POS_WIDTH(8), UI_POS_HEIGHT(0.5)},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
|
ImageButton button_play{
|
||||||
|
{UI_POS_X_RIGHT(2), UI_POS_Y(3.25) - 2, UI_POS_WIDTH(2), UI_POS_HEIGHT(0.5)},
|
||||||
|
&bitmap_stop,
|
||||||
|
Theme::getInstance()->fg_green->foreground,
|
||||||
|
Theme::getInstance()->fg_green->background};
|
||||||
|
|
||||||
TextField field_marker{
|
TextField field_marker{
|
||||||
{12 * 8, 3 * 16, 9 * 8, 16},
|
{UI_POS_X(12), UI_POS_Y(3), UI_POS_WIDTH(9), UI_POS_HEIGHT(1)},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
Button button_marker_minus{
|
Button button_marker_minus{
|
||||||
{7 * 8, 3 * 16 + 4, 1 * 8, 1 * 8},
|
{UI_POS_X(7), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Button button_marker_plus{
|
Button button_marker_plus{
|
||||||
{9 * 8, 3 * 16 + 4, 1 * 8, 1 * 8},
|
{UI_POS_X(9), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
|
||||||
"+"};
|
"+"};
|
||||||
|
|
||||||
NumberField field_trigger{
|
NumberField field_trigger{
|
||||||
{4 * 8, 4 * 16},
|
{UI_POS_X(4), UI_POS_Y(4)},
|
||||||
3,
|
3,
|
||||||
{2, 128},
|
{2, 128},
|
||||||
2,
|
2,
|
||||||
' '};
|
' '};
|
||||||
|
|
||||||
AudioVolumeField field_volume{
|
AudioVolumeField field_volume{
|
||||||
{13 * 8, 4 * 16}};
|
{UI_POS_X(13), UI_POS_Y(4)}};
|
||||||
|
|
||||||
/*OptionsField steps_config{
|
/*OptionsField steps_config{
|
||||||
{13 * 8, 4 * 16},
|
{UI_POS_X(13), UI_POS_Y(4)},
|
||||||
3,
|
3,
|
||||||
{
|
{
|
||||||
{"1", 1},
|
{"1", 1},
|
||||||
@@ -255,7 +265,7 @@ class GlassView : public View {
|
|||||||
}};*/
|
}};*/
|
||||||
|
|
||||||
OptionsField scan_type{
|
OptionsField scan_type{
|
||||||
{17 * 8, 4 * 16},
|
{UI_POS_X(17), UI_POS_Y(4)},
|
||||||
2,
|
2,
|
||||||
{
|
{
|
||||||
{"F-", LOOKING_GLASS_FASTSCAN},
|
{"F-", LOOKING_GLASS_FASTSCAN},
|
||||||
@@ -263,7 +273,7 @@ class GlassView : public View {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
OptionsField view_config{
|
OptionsField view_config{
|
||||||
{19 * 8, 4 * 16},
|
{UI_POS_X(19), UI_POS_Y(4)},
|
||||||
7,
|
7,
|
||||||
{
|
{
|
||||||
{"SPCTR-V", 0},
|
{"SPCTR-V", 0},
|
||||||
@@ -272,7 +282,7 @@ class GlassView : public View {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
OptionsField level_integration{
|
OptionsField level_integration{
|
||||||
{27 * 8, 4 * 16},
|
{UI_POS_X(27), UI_POS_Y(4)},
|
||||||
2,
|
2,
|
||||||
{
|
{
|
||||||
{"x0", 0},
|
{"x0", 0},
|
||||||
@@ -288,15 +298,19 @@ class GlassView : public View {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
Button button_jump{
|
Button button_jump{
|
||||||
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
|
{UI_POS_X_RIGHT(4), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||||
"JMP"};
|
"JMP"};
|
||||||
|
|
||||||
Button button_rst{
|
Button button_rst{
|
||||||
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
|
{UI_POS_X_RIGHT(9), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||||
"RST"};
|
"RST"};
|
||||||
|
|
||||||
|
Button button_rssi{
|
||||||
|
{UI_POS_X_RIGHT(15), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
|
||||||
|
"RSSI"};
|
||||||
|
|
||||||
Text freq_stats{
|
Text freq_stats{
|
||||||
{UI_POS_X(0), 5 * 16, screen_width - 10 * 8, 8},
|
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_spectrum_config{
|
MessageHandlerRegistration message_handler_spectrum_config{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* Copyright (C) 2024 Mark Thompson
|
* Copyright (C) 2024 Mark Thompson
|
||||||
* Copyright (C) 2024 u-foka
|
* Copyright (C) 2024 u-foka
|
||||||
* Copyright (C) 2024 HTotoo
|
* Copyright (C) 2024 HTotoo
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -1163,6 +1163,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
|||||||
&field_battcap,
|
&field_battcap,
|
||||||
&button_help_cap,
|
&button_help_cap,
|
||||||
&checkbox_overridebatt,
|
&checkbox_overridebatt,
|
||||||
|
&checkbox_battery_replaceable,
|
||||||
&checkbox_battery_charge_hint});
|
&checkbox_battery_charge_hint});
|
||||||
|
|
||||||
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
|
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
|
||||||
@@ -1170,14 +1171,20 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
|||||||
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());
|
||||||
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
|
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
|
||||||
|
pmem::set_battery_replaceable(checkbox_battery_replaceable.value());
|
||||||
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
|
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
|
||||||
if (((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) {
|
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||||
|
if (dev) dev->resetChangedFlag();
|
||||||
|
if ((((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) || (dev && dev->getIsBattChanged())) {
|
||||||
pmem::set_battery_cap_mah(field_battcap.value());
|
pmem::set_battery_cap_mah(field_battcap.value());
|
||||||
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
|
||||||
if (dev && !dev->reInit()) {
|
if (dev && !dev->reInit()) {
|
||||||
nav.display_modal("Error", "Battery gauge re-init failed");
|
nav.display_modal("Error", "Battery gauge re-init failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (dev) {
|
||||||
|
dev->resetChangedFlag();
|
||||||
|
dev->update(); // resend the new data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
send_system_refresh();
|
send_system_refresh();
|
||||||
nav.pop();
|
nav.pop();
|
||||||
@@ -1206,6 +1213,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
|||||||
|
|
||||||
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
|
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
|
||||||
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
|
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
|
||||||
|
checkbox_battery_replaceable.set_value(pmem::battery_replaceable());
|
||||||
|
|
||||||
field_battcap.set_value(pmem::battery_cap_mah());
|
field_battcap.set_value(pmem::battery_cap_mah());
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +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
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -1053,7 +1053,7 @@ class SetBatteryView : public View {
|
|||||||
{{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}};
|
{{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Labels labels2{{{UI_POS_X(1), UI_POS_Y(11)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
Labels labels2{{{UI_POS_X(1), UI_POS_Y_BOTTOM(7)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_battcap{
|
NumberField field_battcap{
|
||||||
{UI_POS_X(1), UI_POS_Y(9)},
|
{UI_POS_X(1), UI_POS_Y(9)},
|
||||||
@@ -1078,17 +1078,22 @@ class SetBatteryView : public View {
|
|||||||
23,
|
23,
|
||||||
"Charge hint"};
|
"Charge hint"};
|
||||||
|
|
||||||
|
Checkbox checkbox_battery_replaceable{
|
||||||
|
{UI_POS_X(2), UI_POS_Y(11)},
|
||||||
|
23,
|
||||||
|
"Replaceable battery"};
|
||||||
|
|
||||||
Button button_cancel{
|
Button button_cancel{
|
||||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||||
"Cancel",
|
"Cancel",
|
||||||
};
|
};
|
||||||
|
|
||||||
Button button_save{
|
Button button_save{
|
||||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||||
"Save"};
|
"Save"};
|
||||||
|
|
||||||
Button button_reset{
|
Button button_reset{
|
||||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y(13), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(6), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||||
"Reset",
|
"Reset",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -186,26 +186,26 @@ void set_dirty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standalone_application_api_t api = {
|
standalone_application_api_t api = {
|
||||||
/* .malloc = */ &alloc,
|
.malloc = &alloc,
|
||||||
/* .calloc = */ &calloc,
|
.calloc = &calloc,
|
||||||
/* .realloc = */ &realloc,
|
.realloc = &realloc,
|
||||||
/* .free = */ &chHeapFree,
|
.free = &chHeapFree,
|
||||||
/* .create_thread = */ &create_thread,
|
.create_thread = &create_thread,
|
||||||
/* .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_5x8_glyph_data = ui::font::fixed_5x8.get_data(),
|
||||||
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
|
.fixed_8x16_glyph_data = ui::font::fixed_8x16.get_data(),
|
||||||
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
|
.fill_rectangle_unrolled8 = &fill_rectangle_unrolled8,
|
||||||
/* .draw_bitmap = */ &draw_bitmap,
|
.draw_bitmap = &draw_bitmap,
|
||||||
/* .scroll_area_y = */ &scroll_area_y,
|
.scroll_area_y = &scroll_area_y,
|
||||||
/* .scroll_set_area = */ &scroll_set_area,
|
.scroll_set_area = &scroll_set_area,
|
||||||
/* .scroll_disable = */ &scroll_disable,
|
.scroll_disable = &scroll_disable,
|
||||||
/* .scroll_set_position = */ &scroll_set_position,
|
.scroll_set_position = &scroll_set_position,
|
||||||
/* .scroll = */ &scroll,
|
.scroll = &scroll,
|
||||||
/* .i2c_read = */ &i2c_read,
|
.i2c_read = &i2c_read,
|
||||||
/* .panic = */ &chDbgPanic,
|
.panic = &chDbgPanic,
|
||||||
/* .set_dirty = */ &set_dirty,
|
.set_dirty = &set_dirty,
|
||||||
// Version 3
|
// Version 3
|
||||||
.f_open = &ext_f_open,
|
.f_open = &ext_f_open,
|
||||||
.f_close = &ext_f_close,
|
.f_close = &ext_f_close,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ void SubGhzDRecentEntryDetailView::update_data() {
|
|||||||
console.writeln("Action: 0x" + to_string_hex(func_code));
|
console.writeln("Action: 0x" + to_string_hex(func_code));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry_.sensorType == FPS_KEELOQ) {
|
if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) {
|
||||||
console.writeln("Fix: " + to_string_hex(fix));
|
console.writeln("Fix: " + to_string_hex(fix));
|
||||||
console.writeln("Encrypted: " + to_string_hex(encrypted));
|
console.writeln("Encrypted: " + to_string_hex(encrypted));
|
||||||
console.writeln("Manufacturer: " + mf_name);
|
console.writeln("Manufacturer: " + mf_name);
|
||||||
@@ -243,6 +243,8 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
|
|||||||
return "Ido 11x";
|
return "Ido 11x";
|
||||||
case FPS_INTERTECHNOV3:
|
case FPS_INTERTECHNOV3:
|
||||||
return "InterTehcno v3";
|
return "InterTehcno v3";
|
||||||
|
case FPS_SUPERROLLO:
|
||||||
|
return "Superrollo";
|
||||||
case FPS_KEELOQ:
|
case FPS_KEELOQ:
|
||||||
return "KeeLoq";
|
return "KeeLoq";
|
||||||
case FPS_KINGGATESSTYLO4K:
|
case FPS_KINGGATESSTYLO4K:
|
||||||
@@ -671,7 +673,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry_.sensorType == FPS_KEELOQ) {
|
if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) {
|
||||||
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
|
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
|
||||||
|
|
||||||
btn = data_rev >> 60;
|
btn = data_rev >> 60;
|
||||||
|
|||||||
@@ -32,10 +32,6 @@
|
|||||||
|
|
||||||
#include "core_control.hpp"
|
#include "core_control.hpp"
|
||||||
|
|
||||||
/* Set true to enable additional checks to ensure
|
|
||||||
* M4 and M0 are synchronized before passing messages. */
|
|
||||||
static constexpr bool enforce_core_sync = true;
|
|
||||||
|
|
||||||
/* Set true to enable check for baseband messages getting stuck.
|
/* Set true to enable check for baseband messages getting stuck.
|
||||||
* This implies the baseband thread is not dequeuing and has probably stalled.
|
* This implies the baseband thread is not dequeuing and has probably stalled.
|
||||||
* NB: This check adds a small amout of overhead to the message sending code
|
* NB: This check adds a small amout of overhead to the message sending code
|
||||||
@@ -343,6 +339,11 @@ void set_spectrum(
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_audio_ddc_frequency(int32_t frequency) {
|
||||||
|
const AudioDDCConfigMessage message{frequency};
|
||||||
|
send_message(&message);
|
||||||
|
}
|
||||||
|
|
||||||
void set_time_sink(
|
void set_time_sink(
|
||||||
const size_t sampling_rate,
|
const size_t sampling_rate,
|
||||||
const size_t trigger) {
|
const size_t trigger) {
|
||||||
@@ -361,6 +362,16 @@ void set_noaaapt_config() {
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_vor_config(bool enabled) {
|
||||||
|
const VorRxConfigureMessage message{enabled};
|
||||||
|
send_message(&message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled, const std::string& ident, bool enabled) {
|
||||||
|
const VorTxConfigureMessage message{radial_deg, ident_enabled, ident.c_str(), enabled};
|
||||||
|
send_message(&message);
|
||||||
|
}
|
||||||
|
|
||||||
void set_flex_config() {
|
void set_flex_config() {
|
||||||
const FlexConfigureMessage message{};
|
const FlexConfigureMessage message{};
|
||||||
send_message(&message);
|
send_message(&message);
|
||||||
@@ -445,13 +456,21 @@ void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length) {
|
|||||||
send_message(&msg);
|
send_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start) {
|
||||||
|
HunterConfigMessage message{};
|
||||||
|
message.energy_threshold = threshold;
|
||||||
|
message.hangtime_ms = hangtime_ms;
|
||||||
|
message.start = start;
|
||||||
|
send_message(&message);
|
||||||
|
}
|
||||||
|
|
||||||
static bool baseband_image_running = false;
|
static bool baseband_image_running = false;
|
||||||
|
|
||||||
bool is_image_running() {
|
bool is_image_running() {
|
||||||
return baseband_image_running;
|
return baseband_image_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_image(const spi_flash::image_tag_t image_tag) {
|
void run_image(const spi_flash::image_tag_t image_tag, bool enforce_core_sync) {
|
||||||
if (baseband_image_running) {
|
if (baseband_image_running) {
|
||||||
chDbgPanic("BBRunning");
|
chDbgPanic("BBRunning");
|
||||||
}
|
}
|
||||||
@@ -464,7 +483,7 @@ void run_image(const spi_flash::image_tag_t image_tag) {
|
|||||||
|
|
||||||
creg::m4txevent::enable();
|
creg::m4txevent::enable();
|
||||||
|
|
||||||
if constexpr (enforce_core_sync) {
|
if (enforce_core_sync) {
|
||||||
// Wait up to 3 seconds for baseband to start handling events.
|
// Wait up to 3 seconds for baseband to start handling events.
|
||||||
auto count = 3'000u;
|
auto count = 3'000u;
|
||||||
while (!shared_memory.baseband_ready && --count)
|
while (!shared_memory.baseband_ready && --count)
|
||||||
@@ -475,7 +494,7 @@ void run_image(const spi_flash::image_tag_t image_tag) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_prepared_image(const uint32_t m4_code) {
|
void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync) {
|
||||||
if (baseband_image_running) {
|
if (baseband_image_running) {
|
||||||
chDbgPanic("BBRunning");
|
chDbgPanic("BBRunning");
|
||||||
}
|
}
|
||||||
@@ -488,7 +507,7 @@ void run_prepared_image(const uint32_t m4_code) {
|
|||||||
|
|
||||||
creg::m4txevent::enable();
|
creg::m4txevent::enable();
|
||||||
|
|
||||||
if constexpr (enforce_core_sync) {
|
if (enforce_core_sync) {
|
||||||
// Wait up to 3 seconds for baseband to start handling events.
|
// Wait up to 3 seconds for baseband to start handling events.
|
||||||
auto count = 3'000u;
|
auto count = 3'000u;
|
||||||
while (!shared_memory.baseband_ready && --count)
|
while (!shared_memory.baseband_ready && --count)
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ void set_rds_data(const uint16_t message_length);
|
|||||||
void set_spectrum(
|
void set_spectrum(
|
||||||
const size_t sampling_rate,
|
const size_t sampling_rate,
|
||||||
const size_t trigger);
|
const size_t trigger);
|
||||||
|
void set_audio_ddc_frequency(int32_t frequency);
|
||||||
void set_time_sink(
|
void set_time_sink(
|
||||||
const size_t sampling_rate,
|
const size_t sampling_rate,
|
||||||
const size_t trigger);
|
const size_t trigger);
|
||||||
@@ -113,6 +114,8 @@ void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta);
|
|||||||
void set_morsetx_key(bool key_down);
|
void set_morsetx_key(bool key_down);
|
||||||
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
||||||
void set_noaaapt_config();
|
void set_noaaapt_config();
|
||||||
|
void set_vor_config(bool enabled = true);
|
||||||
|
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled = true, const std::string& ident = "", bool enabled = true);
|
||||||
void set_flex_config();
|
void set_flex_config();
|
||||||
void set_bitstream_config(uint32_t deviation, uint8_t mode); // mode 0 for am, 1 for 2fsk
|
void set_bitstream_config(uint32_t deviation, uint8_t mode); // mode 0 for am, 1 for 2fsk
|
||||||
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload = nullptr, uint16_t payload_length = 0); // baud*100
|
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload = nullptr, uint16_t payload_length = 0); // baud*100
|
||||||
@@ -120,6 +123,7 @@ void set_rtty_config(RTTYDataMessage& message);
|
|||||||
void set_epirb_tx_config(EPIRBTXDataMessage& message);
|
void set_epirb_tx_config(EPIRBTXDataMessage& message);
|
||||||
void set_epirb_rx_config(EPIRBRXConfig& message);
|
void set_epirb_rx_config(EPIRBRXConfig& message);
|
||||||
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length);
|
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length);
|
||||||
|
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start);
|
||||||
|
|
||||||
void request_roger_beep();
|
void request_roger_beep();
|
||||||
void request_rssi_beep();
|
void request_rssi_beep();
|
||||||
@@ -127,8 +131,8 @@ void request_beep_stop();
|
|||||||
void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms);
|
void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms);
|
||||||
|
|
||||||
bool is_image_running();
|
bool is_image_running();
|
||||||
void run_image(const portapack::spi_flash::image_tag_t image_tag);
|
void run_image(const portapack::spi_flash::image_tag_t image_tag, bool enforce_core_sync = true);
|
||||||
void run_prepared_image(const uint32_t m4_code);
|
void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync = true);
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
void spectrum_streaming_start();
|
void spectrum_streaming_start();
|
||||||
|
|||||||
@@ -25,12 +25,19 @@
|
|||||||
#include "portapack_io.hpp"
|
#include "portapack_io.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
|
|
||||||
|
#include "lpc43xx.inc"
|
||||||
|
|
||||||
#include "hackrf_hal.hpp"
|
#include "hackrf_hal.hpp"
|
||||||
using namespace hackrf::one;
|
using namespace hackrf::one;
|
||||||
|
|
||||||
#include "lpc43xx_cpp.hpp"
|
#include "lpc43xx_cpp.hpp"
|
||||||
using namespace lpc43xx;
|
using namespace lpc43xx;
|
||||||
|
|
||||||
|
#ifdef PRALINE
|
||||||
|
#include "hackrf_gpio.hpp"
|
||||||
|
#include "gpio.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "fpga_bridge.h"
|
#include "fpga_bridge.h"
|
||||||
@@ -325,6 +332,12 @@ static constexpr ClockControl::MultiSynthSource get_si5351a_reference_clock_gene
|
|||||||
? ClockControl::MultiSynthSource::PLLA
|
? ClockControl::MultiSynthSource::PLLA
|
||||||
: ClockControl::MultiSynthSource::PLLB;
|
: ClockControl::MultiSynthSource::PLLB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||||
|
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||||
|
? ClockControl::MultiSynthSource::PLLA
|
||||||
|
: ClockControl::MultiSynthSource::PLLB;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||||
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||||
@@ -406,7 +419,11 @@ std::string ClockManager::get_freq() {
|
|||||||
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
|
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void portapack_tcxo_enable() {
|
void ClockManager::portapack_tcxo_enable() {
|
||||||
|
#ifdef PRALINE
|
||||||
|
gpio_control::clkin_ctrl.setActive();
|
||||||
|
set_p1_control(P1_Function::P22_ClkIn);
|
||||||
|
#endif
|
||||||
portapack::io.reference_oscillator(true);
|
portapack::io.reference_oscillator(true);
|
||||||
|
|
||||||
/* Delay >10ms at 96MHz clock speed for reference oscillator to start. */
|
/* Delay >10ms at 96MHz clock speed for reference oscillator to start. */
|
||||||
@@ -415,7 +432,10 @@ static void portapack_tcxo_enable() {
|
|||||||
while (delay--);
|
while (delay--);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void portapack_tcxo_disable() {
|
void ClockManager::portapack_tcxo_disable() {
|
||||||
|
#ifdef PRALINE
|
||||||
|
gpio_control::clkin_ctrl.setInactive();
|
||||||
|
#endif
|
||||||
portapack::io.reference_oscillator(false);
|
portapack::io.reference_oscillator(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,77 +443,50 @@ static void portapack_tcxo_disable() {
|
|||||||
using namespace hackrf::one;
|
using namespace hackrf::one;
|
||||||
|
|
||||||
void ClockManager::init_clock_generator() {
|
void ClockManager::init_clock_generator() {
|
||||||
#ifdef PRALINE
|
|
||||||
// thoese PIN can review platform_scu file
|
|
||||||
// have many conflict and modify for clock init
|
|
||||||
// P2_10 -> GPIO0[14] P1_CTRL0
|
|
||||||
LPC_SCU->SFSP[2][10] = 0xF0;
|
|
||||||
// P6_8 -> GPIO5[16] P1_CTRL1
|
|
||||||
LPC_SCU->SFSP[6][8] = 0xF4;
|
|
||||||
// P6_9 -> GPIO3[5] P1_CTRL2
|
|
||||||
LPC_SCU->SFSP[6][9] = 0xF0;
|
|
||||||
// P1_20 -> GPIO0[15] CLKIN_CTRL
|
|
||||||
LPC_SCU->SFSP[1][20] = 0xF0;
|
|
||||||
|
|
||||||
constexpr GPIO gpio_p1_ctrl0 = gpio[GPIO0_14];
|
|
||||||
constexpr GPIO gpio_p1_ctrl1 = gpio[GPIO5_16];
|
|
||||||
constexpr GPIO gpio_p1_ctrl2 = gpio[GPIO3_5];
|
|
||||||
constexpr GPIO gpio_clkin_ctrl = gpio[GPIO0_15];
|
|
||||||
|
|
||||||
gpio_p1_ctrl0.output();
|
|
||||||
gpio_p1_ctrl1.output();
|
|
||||||
gpio_p1_ctrl2.output();
|
|
||||||
gpio_clkin_ctrl.output();
|
|
||||||
|
|
||||||
// P1 control = 100 and choose P22 clock external clock on portapack
|
|
||||||
gpio_p1_ctrl0.write(0);
|
|
||||||
gpio_p1_ctrl1.write(0);
|
|
||||||
gpio_p1_ctrl2.write(1);
|
|
||||||
gpio_clkin_ctrl.write(1);
|
|
||||||
chThdSleepMilliseconds(20);
|
|
||||||
|
|
||||||
#else
|
|
||||||
// HackRF One r9: GPIO0_8 (mcu_clk_en) gates Si5351 CLK2/CLK7 to GP_CLKIN
|
|
||||||
if (hackrf_r9) {
|
|
||||||
gpio_r9_mcu_clk_en.output();
|
|
||||||
gpio_r9_mcu_clk_en.write(1);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
clock_generator.reset();
|
clock_generator.reset();
|
||||||
clock_generator.set_crystal_internal_load_capacitance(CrystalInternalLoadCapacitance::XTAL_CL_8pF);
|
clock_generator.set_crystal_internal_load_capacitance(CrystalInternalLoadCapacitance::XTAL_CL_8pF);
|
||||||
clock_generator.enable_fanout();
|
clock_generator.enable_fanout();
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* PRALINE has Si5351A (NOT Si5351C like HackRF One OG).
|
|
||||||
* Must use Si5351A configuration: PLLA only, no CLKIN support.
|
|
||||||
*
|
|
||||||
* IMPORTANT: Follow HackRF reference sequence:
|
|
||||||
* 1. Set PLL input sources
|
|
||||||
* 2. Configure PLL and multisynths
|
|
||||||
* 3. Set clock control registers (AFTER multisynths!)
|
|
||||||
* 4. Reset PLLs
|
|
||||||
* 5. Enable outputs
|
|
||||||
*/
|
|
||||||
// change there detect method same as hackrf one
|
|
||||||
// PLLA -> XTAL,PLLB -> CLKIN
|
|
||||||
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
|
||||||
// although the name a and the branch define CLK2 MCU CLOCK
|
|
||||||
// only use CLK2
|
|
||||||
auto si5351_clock_control_common = si5351a_clock_control_common;
|
|
||||||
constexpr size_t clock_generator_output_pro_mcu_clkin = 2;
|
constexpr size_t clock_generator_output_pro_mcu_clkin = 2;
|
||||||
// clk_src define CLKIN
|
auto si5351_clock_control_common = si5351a_clock_control_common;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Match the HackRF One init flow:
|
||||||
|
* 1. PLLA stays on the local 25MHz crystal.
|
||||||
|
* 2. PLLB is configured from the Si5351 CLKIN pin.
|
||||||
|
* 3. CLK2 temporarily drives GP_CLKIN directly from CLKIN so we can
|
||||||
|
* detect whether P22 is carrying a valid 10MHz reference.
|
||||||
|
* 4. Once the source is chosen, all runtime clocks are switched to the
|
||||||
|
* selected PLL and the working multisynths are programmed.
|
||||||
|
*/
|
||||||
|
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
||||||
|
|
||||||
clock_generator.set_clock_control(
|
clock_generator.set_clock_control(
|
||||||
clock_generator_output_pro_mcu_clkin,
|
clock_generator_output_pro_mcu_clkin,
|
||||||
si5351_clock_control_common[clock_generator_output_pro_mcu_clkin]
|
si5351_clock_control_common[clock_generator_output_pro_mcu_clkin]
|
||||||
.clk_src(ClockControl::ClockSource::CLKIN)
|
.clk_src(ClockControl::ClockSource::CLKIN)
|
||||||
.clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
.clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
||||||
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||||
chThdSleepMilliseconds(20);
|
|
||||||
// should be extern icon and 10MHz
|
|
||||||
reference = choose_reference();
|
|
||||||
|
|
||||||
/* Clock control will be set AFTER multisynth configuration - see below */
|
chThdSleepMilliseconds(20);
|
||||||
|
reference = choose_reference();
|
||||||
|
clock_generator.disable_output(clock_generator_output_pro_mcu_clkin);
|
||||||
|
|
||||||
|
const auto ref_pll =
|
||||||
|
get_si5351c_reference_clock_generator_pll(reference.source);
|
||||||
|
|
||||||
|
const ClockControls si5351_clock_control = ClockControls{{
|
||||||
|
si5351_clock_control_common[0].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[1].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[2].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[3].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[4].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[5].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[6].ms_src(ref_pll),
|
||||||
|
si5351_clock_control_common[7].ms_src(ref_pll),
|
||||||
|
}};
|
||||||
|
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||||
#else
|
#else
|
||||||
clock_generator.set_pll_input_sources(hackrf_r9
|
clock_generator.set_pll_input_sources(hackrf_r9
|
||||||
? si5351a_pll_input_sources
|
? si5351a_pll_input_sources
|
||||||
@@ -538,86 +531,50 @@ void ClockManager::init_clock_generator() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
|
clock_generator.write_pll_single_byte(0, si5351_pll_a_afe_800m);
|
||||||
|
|
||||||
/* * Praline HackRF Pro Clock Assignments (800 MHz VCO Configuration)
|
|
||||||
* VCO Frequency: 800,000,000 Hz (Master Reference)
|
|
||||||
* * CLK0: AFE_CLK (MAX5864 Codec & FPGA ADC Interface)
|
|
||||||
* - Note: Defines hardware sample rate. Essential for WFM purity.
|
|
||||||
* * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock)
|
|
||||||
* - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes.
|
|
||||||
* * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input)
|
|
||||||
* - Note: Synchronizes MCU processing to the RF clock tree.
|
|
||||||
* * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 1
|
|
||||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
|
||||||
* * CLK4: P_CLK (MAX2831 Peripheral/Expansion Clock)
|
|
||||||
* - Note: Routed to expansion headers for external hardware sync.
|
|
||||||
* * CLK5: AUX_CLK (RFFC5371 Auxiliary reference for secondary logic)
|
|
||||||
* - Note: Provides additional timing flexibility for the iCE40 FPGA.
|
|
||||||
* * CLK6: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 2
|
|
||||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
|
||||||
* * CLK7: Unused / Power-Down
|
|
||||||
* - State: Disabled (si5351a_ms6_7_off_reg)
|
|
||||||
* - Note: Kept OFF to reduce EMI/RFI near the RF front-end.
|
|
||||||
* * CLKOUT: Optional external clock output on the header.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Write PLL A (800 MHz based on 25 MHz xtal for RF) and
|
|
||||||
* PLL B (800 MHZ based on 25 MHz xtalfor Digital)
|
|
||||||
* Use single-byte writes to debug I2C issues
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
// Write PLLA (Registers 26-33)
|
const auto& pll_b = si5351c_pll_b_clkin_reg;
|
||||||
/* Write PLL A configuration (Base 26) */
|
|
||||||
const auto& pll_a = si5351_pll_a_800_reg;
|
|
||||||
for (size_t i = 1; i < pll_a.size(); i++) {
|
|
||||||
clock_generator.write_register(pll_a[0] + i - 1, pll_a[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write PLLB (Registers 34-41)
|
|
||||||
/* Write PLL B configuration (Base 34) */
|
|
||||||
const auto& pll_b = si5351_pll_b_800_reg;
|
|
||||||
for (size_t i = 1; i < pll_b.size(); i++) {
|
for (size_t i = 1; i < pll_b.size(); i++) {
|
||||||
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
|
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// CLK0: DAFE_CLK initial 4 MHz
|
||||||
|
clock_generator.write_ms_single_byte(
|
||||||
|
0,
|
||||||
|
si5351_ms_afe_4m);
|
||||||
|
|
||||||
/* Write multisynth configurations using single-byte writes */
|
// CLK1: DSCT_CLK initial 10 MHz
|
||||||
// These cover all active channels on the Praline board
|
clock_generator.write_ms_single_byte(
|
||||||
clock_generator.write_ms_single_byte(0, si5351_ms_afe_4m); // CLK0: PLL A AFE Codec (4 MHz)
|
1,
|
||||||
clock_generator.write_ms_single_byte(1, si5351_ms_10m); // CLK1: PLL B SGPIO/FPGA Timing (10 MHz)
|
si5351_ms_10m);
|
||||||
clock_generator.write_ms_single_byte(2, si5351_ms_afe_40m); // CLK2: PLL A Audio and MCU Input (40 MHz)
|
|
||||||
clock_generator.write_ms_single_byte(3, si5351_ms_0_4m); // CLK3: PLL B SMA Port 1 Logic Sync (4 MHz or 10 MHz)
|
|
||||||
clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A MAX2831 Second IF (40 MHz)
|
|
||||||
clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A RFFC5071First IF (40 MHz)
|
|
||||||
clock_generator.write_ms_single_byte(6, si5351_ms_0_4m); // CLK6: PLL B SMA Port 2 Logic Sync (4 MHz or 10 MHz)
|
|
||||||
clock_generator.write_ms_single_byte(7, si5351_ms_0_4m); // CLK7: PLL B Unused (4 MHz)
|
|
||||||
|
|
||||||
/* NOW set clock control registers (AFTER multisynths per HackRF reference) */
|
// CLK2: MCU_CLK 40 MHz
|
||||||
const auto ref_pll_a = ClockControl::MultiSynthSource::PLLA;
|
clock_generator.write_ms_single_byte(
|
||||||
const auto ref_pll_b = ClockControl::MultiSynthSource::PLLB;
|
2,
|
||||||
const ClockControls si5351_clock_control = ClockControls{{
|
si5351_ms_afe_40m);
|
||||||
si5351a_clock_control_common[0].ms_src(ref_pll_a),
|
|
||||||
si5351a_clock_control_common[1].ms_src(ref_pll_b),
|
|
||||||
si5351a_clock_control_common[2].ms_src(ref_pll_a),
|
|
||||||
si5351a_clock_control_common[3].ms_src(ref_pll_b),
|
|
||||||
si5351a_clock_control_common[4].ms_src(ref_pll_a),
|
|
||||||
si5351a_clock_control_common[5].ms_src(ref_pll_a),
|
|
||||||
si5351a_clock_control_common[6].ms_src(ref_pll_b),
|
|
||||||
si5351a_clock_control_common[7].ms_src(ref_pll_b),
|
|
||||||
}};
|
|
||||||
// clock_generator.set_clock_control(si5351_clock_control);
|
|
||||||
// Use single-byte writes instead of multi-byte
|
|
||||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
|
||||||
|
|
||||||
// Don't write CLKS 3, 6, and 7 multisynth
|
// CLK3: P2 clock mux, disabled below
|
||||||
// Ensure CLK3 clock control has Power_Off
|
clock_generator.write_ms_single_byte(
|
||||||
// Verify output is disabled
|
3,
|
||||||
clock_generator.disable_output(3);
|
si5351_ms_10m);
|
||||||
clock_generator.disable_clock(3);
|
|
||||||
clock_generator.disable_output(6);
|
// CLK4: DXCVR_CLK 40 MHz
|
||||||
clock_generator.disable_clock(6);
|
clock_generator.write_ms_single_byte(
|
||||||
clock_generator.disable_output(7);
|
4,
|
||||||
clock_generator.disable_clock(7);
|
si5351_ms_afe_40m);
|
||||||
|
|
||||||
|
// CLK5: DMIX_CLK 40 MHz
|
||||||
|
clock_generator.write_ms_single_byte(
|
||||||
|
5,
|
||||||
|
si5351_ms_afe_40m);
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto& ms6_7 = si5351a_ms6_7_off_reg;
|
||||||
|
for (size_t i = 1; i < ms6_7.size(); i++) {
|
||||||
|
clock_generator.write_register(ms6_7[0] + i - 1, ms6_7[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
||||||
@@ -645,15 +602,14 @@ void ClockManager::init_clock_generator() {
|
|||||||
|
|
||||||
// Wait for PLL(s) to lock.
|
// Wait for PLL(s) to lock.
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
// PRALINE: Wait for 0x60 (0x20 | 0x40), PLLA and PLLB to lock (0x20 = LOL_A bit, 0x40 = LOL_B bit)
|
uint8_t device_status_mask =
|
||||||
uint8_t device_status_mask = 0x60;
|
(ref_pll == ClockControl::MultiSynthSource::PLLB) ? 0x40 : 0x20;
|
||||||
uint32_t pll_timeout = 100000;
|
uint32_t pll_timeout = 100000;
|
||||||
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
|
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
|
||||||
pll_timeout--;
|
pll_timeout--;
|
||||||
}
|
}
|
||||||
// Store PLL lock status for debugging
|
|
||||||
static volatile uint32_t pll_lock_timeout = pll_timeout;
|
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||||
(void)pll_lock_timeout;
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Wait for PLL(s) to lock - with timeout to prevent hang
|
// Wait for PLL(s) to lock - with timeout to prevent hang
|
||||||
@@ -724,7 +680,7 @@ ClockManager::Reference ClockManager::choose_reference() {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
gpio_r9_clkin_en.write(1);
|
gpio_control::r9_clkin_en.setActive();
|
||||||
volatile uint32_t delay = 240000 + 24000;
|
volatile uint32_t delay = 240000 + 24000;
|
||||||
while (delay--);
|
while (delay--);
|
||||||
}
|
}
|
||||||
@@ -739,7 +695,7 @@ ClockManager::Reference ClockManager::choose_reference() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
gpio_r9_clkin_en.write(0);
|
gpio_control::r9_clkin_en.setInactive();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -753,13 +709,13 @@ void ClockManager::shutdown() {
|
|||||||
|
|
||||||
void ClockManager::enable_codec_clocks() {
|
void ClockManager::enable_codec_clocks() {
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* PRALINE: CLK0 (AFE_CLK) for codec/FPGA, CLK1 (SCT_CLK) for FPGA timing.
|
/* PRALINE: only gate the clocks owned by the RF datapath here.
|
||||||
* Reference hackrf_core.c shows PRALINE needs both CLK0 and CLK1. */
|
* CLK2 is MCU_CLKIN and must stay independent of runtime RX/TX clock
|
||||||
|
* management or the SGPIO/GPIO subsystem can glitch mid-stream. */
|
||||||
clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/
|
clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/
|
||||||
clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/
|
clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/
|
||||||
clock_generator.enable_clock(clock_generator_output_og_sgpio); /* CLK2 LPC43xx*/
|
|
||||||
clock_generator.enable_output_mask(
|
clock_generator.enable_output_mask(
|
||||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
clock_generator.enable_clock(clock_generator_output_r9_sgpio);
|
clock_generator.enable_clock(clock_generator_output_r9_sgpio);
|
||||||
@@ -787,12 +743,11 @@ void ClockManager::disable_codec_clocks() {
|
|||||||
* CLKx_DISABLE_STATE.
|
* CLKx_DISABLE_STATE.
|
||||||
*/
|
*/
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* PRALINE: CLK0 (AFE_CLK), CLK1 (SCT_CLK), and CLK2 MCU used for codec/FPGA */
|
/* PRALINE: leave CLK2/MCU_CLKIN alone; only disable datapath-owned clocks. */
|
||||||
clock_generator.disable_output_mask(
|
clock_generator.disable_output_mask(
|
||||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||||
clock_generator.disable_clock(clock_generator_output_og_codec);
|
clock_generator.disable_clock(clock_generator_output_og_codec);
|
||||||
clock_generator.disable_clock(clock_generator_output_og_cpld);
|
clock_generator.disable_clock(clock_generator_output_og_cpld);
|
||||||
clock_generator.disable_clock(clock_generator_output_og_sgpio);
|
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio);
|
clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio);
|
||||||
@@ -851,7 +806,7 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
|||||||
/*
|
/*
|
||||||
* PRALINE sample rate strategy:
|
* PRALINE sample rate strategy:
|
||||||
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
||||||
* 2. Use FPGA decimation to achieve desired output rate
|
* 2. Use FPGA decimation/interpolation to achieve desired output rate
|
||||||
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -872,50 +827,108 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
|||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
_resampling_n = n;
|
/* The TX gateware only implements interpolation factors x1..x16
|
||||||
|
* (tx_intrp 0..4). The rate search above can select n == 5 (x32) for very
|
||||||
// === Stop FPGA processing and flush filters ===
|
* low TX sample rates. If we kept afe_rate/CLK0/CLK1 at the x32 rate while
|
||||||
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
* the FPGA only interpolated by x16, the TX datapath would be clocked at
|
||||||
|
* twice the gateware's output rate, doubling the waveform speed. Cap the
|
||||||
// Verify we're in RX mode before writing RX registers
|
* AFE rate and _resampling_n to the x16 limit in TX so the clocks and the
|
||||||
if (fpga_get_mode() != FPGA_MODE_RX) {
|
* interpolation setting stay aligned. */
|
||||||
// Either set mode or return error
|
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||||
fpga_set_mode(FPGA_MODE_RX);
|
constexpr uint8_t MAX_TX_N = 4; // x16, matches max tx_intrp
|
||||||
|
if (n > MAX_TX_N) {
|
||||||
|
n = MAX_TX_N;
|
||||||
|
afe_rate = frequency << n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set FPGA RX decimation register
|
_resampling_n = n;
|
||||||
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
|
||||||
|
|
||||||
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
|
|
||||||
* We shift up by (3 * n) to compensate for CIC bit-growth.
|
|
||||||
* Relationship: ds = (Stages * n) - Offset
|
|
||||||
* For a 3-stage filter, every increment of n grows the signal by 3 bits.
|
|
||||||
* We subtract a baseline offset to keep the signal within 8-bit bounds.
|
|
||||||
* Add a baseline shift to ensure the signal isn't too quiet
|
|
||||||
*/
|
|
||||||
uint8_t ds = (3 * n);
|
|
||||||
ds += 2;
|
|
||||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
|
||||||
|
|
||||||
radio::invalidate_spi_config();
|
radio::invalidate_spi_config();
|
||||||
|
|
||||||
// Configure Si5351 clocks
|
|
||||||
// CLK0: AFE_CLK (with r_div=1 for ÷2)
|
|
||||||
// CLK1: SCT_CLK (with r_div=0 for ÷1, runs at 2× AFE for FPGA timing)
|
|
||||||
// Configure Si5351 clocks using the correct AFE VCO
|
// Configure Si5351 clocks using the correct AFE VCO
|
||||||
|
// CLK0 always drives the AFE/MAX5864 clock.
|
||||||
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
|
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
|
||||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
|
||||||
|
|
||||||
// === Reset PLL A for phase alignment ===
|
/* Do not reset PLLA here. On PRALINE, CLK2/MCU_CLKIN is sourced from PLLA,
|
||||||
clock_generator.write_register(si5351::Register::PLLReset, 0x20);
|
* so a runtime PLLA reset can glitch the LPC43xx peripheral clock tree and
|
||||||
|
* break SGPIO-driven RX apps such as ADSB/APRS. Updating the multisynths is
|
||||||
|
* sufficient for sample-rate changes. */
|
||||||
|
|
||||||
// Brief delay for PLL lock and clock stability ===
|
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||||
// ~1ms at 96MHz = ~96000 cycles, use 10ms for safety
|
/*
|
||||||
volatile uint32_t delay = 240000; // ~2.5ms
|
* TX path:
|
||||||
while (delay--);
|
* Baseband generators such as AFSK still synthesize samples at their
|
||||||
|
* legacy logical sample rates (for APRS this is 1.536MHz). On PRALINE,
|
||||||
|
* the FPGA must interpolate those samples up to the active AFE rate.
|
||||||
|
*
|
||||||
|
* The TX datapath is clocked from CLK1/fpgaclk. Therefore CLK1 must
|
||||||
|
* equal the desired complex sample rate at the DAC side. Driving CLK1
|
||||||
|
* at 2x the intended TX sample rate causes the entire APRS waveform to
|
||||||
|
* run twice as fast, which matches the "energy present, undecodable"
|
||||||
|
* symptom seen on HackRF One receivers.
|
||||||
|
*
|
||||||
|
* Gateware mapping from standard.py:
|
||||||
|
* tx_intrp = 0 -> x1
|
||||||
|
* tx_intrp = 1 -> x2
|
||||||
|
* tx_intrp = 2 -> x4
|
||||||
|
* tx_intrp = 3 -> x8
|
||||||
|
* tx_intrp = 4 -> x16
|
||||||
|
*/
|
||||||
|
if (fpga_get_mode() != FPGA_MODE_TX) {
|
||||||
|
fpga_set_mode(FPGA_MODE_TX);
|
||||||
|
}
|
||||||
|
|
||||||
// Re-enable FPGA processing with clean state ===
|
// TX gateware consumes complex samples at the rate presented on CLK1.
|
||||||
fpga_debug_register_write(1, 0x01);
|
clock_generator.set_ms_frequency(1, afe_rate, si5351_vco_afe_f, 0);
|
||||||
|
|
||||||
|
uint8_t tx_interp = 0;
|
||||||
|
uint32_t tx_rate = frequency;
|
||||||
|
while ((tx_rate < afe_rate) && (tx_interp < 4)) {
|
||||||
|
tx_rate <<= 1;
|
||||||
|
tx_interp++;
|
||||||
|
}
|
||||||
|
fpga_tx_set_interpolation(tx_interp);
|
||||||
|
fpga_tx_set_nco_enable(false);
|
||||||
|
fpga_tx_set_phase_step(0);
|
||||||
|
}
|
||||||
|
// for RX mode
|
||||||
|
else {
|
||||||
|
// RX path keeps CLK1 at 2x AFE for receive timing / SGPIO alignment.
|
||||||
|
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||||
|
|
||||||
|
// === Stop FPGA processing and flush filters ===
|
||||||
|
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
||||||
|
|
||||||
|
if (fpga_get_mode() != FPGA_MODE_RX) {
|
||||||
|
fpga_set_mode(FPGA_MODE_RX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set FPGA RX decimation register
|
||||||
|
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
||||||
|
|
||||||
|
/* No RX digital-gain register is written here.
|
||||||
|
*
|
||||||
|
* Register 0x03 used to be programmed with (3 * n + 2) as a "CIC
|
||||||
|
* bit-growth" renormalisation. The gateware has no such register: the
|
||||||
|
* RX decimator is a chain of unity-gain half-band FIRs selected by
|
||||||
|
* rx_decim (fpga/top/standard.py), and 0x03 is rx_pstep, whose top two
|
||||||
|
* bits are the quarter-rate shift. Writing a gain here silently
|
||||||
|
* cancelled the shift that set_tuning_frequency() had programmed, which
|
||||||
|
* left the analogue passband offset with no matching rotation.
|
||||||
|
*
|
||||||
|
* The shift depends on the AFE rate we just chose, so re-apply it after
|
||||||
|
* the rate change. ReceiverModel::update_sampling_rate() calls
|
||||||
|
* update_tuning_frequency() straight after this, which does exactly
|
||||||
|
* that; the write below only keeps the register consistent in between.
|
||||||
|
*/
|
||||||
|
fpga_debug_register_write(
|
||||||
|
FPGA_REG_RX_PSTEP,
|
||||||
|
(radio::debug::get_cached_quarter_shift() & 0b11) << FPGA_RX_QUARTER_SHIFT_SHIFT);
|
||||||
|
|
||||||
|
// Re-enable FPGA processing with clean state ===
|
||||||
|
fpga_debug_register_write(1, 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
||||||
@@ -1181,8 +1194,7 @@ void ClockManager::enable_clock_output(bool enable) {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
gpio_r9_clkout_en.output();
|
gpio_control::r9_clkout_en.setState(enable);
|
||||||
gpio_r9_clkout_en.write(enable);
|
|
||||||
|
|
||||||
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
|
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
|
||||||
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
|
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
|
||||||
@@ -1210,3 +1222,74 @@ void ClockManager::enable_clock_output(bool enable) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PRALINE
|
||||||
|
|
||||||
|
void ClockManager::set_p1_control(P1_Function func) {
|
||||||
|
// Truth table based on P1_Control.csv (L=setInactive, H=setActive)
|
||||||
|
switch (func) {
|
||||||
|
case P1_Function::TriggerIn:
|
||||||
|
gpio_control::p1_ctrl2.setInactive();
|
||||||
|
gpio_control::p1_ctrl1.setInactive();
|
||||||
|
gpio_control::p1_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P1_Function::AuxClk1:
|
||||||
|
gpio_control::p1_ctrl2.setInactive();
|
||||||
|
gpio_control::p1_ctrl1.setInactive();
|
||||||
|
gpio_control::p1_ctrl0.setActive();
|
||||||
|
break;
|
||||||
|
case P1_Function::ClkIn:
|
||||||
|
gpio_control::p1_ctrl2.setInactive();
|
||||||
|
gpio_control::p1_ctrl1.setActive();
|
||||||
|
gpio_control::p1_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P1_Function::TriggerOut:
|
||||||
|
gpio_control::p1_ctrl2.setInactive();
|
||||||
|
gpio_control::p1_ctrl1.setActive();
|
||||||
|
gpio_control::p1_ctrl0.setActive();
|
||||||
|
break;
|
||||||
|
case P1_Function::P22_ClkIn:
|
||||||
|
gpio_control::p1_ctrl2.setActive();
|
||||||
|
gpio_control::p1_ctrl1.setInactive();
|
||||||
|
gpio_control::p1_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P1_Function::P2_5:
|
||||||
|
gpio_control::p1_ctrl2.setActive();
|
||||||
|
gpio_control::p1_ctrl1.setInactive();
|
||||||
|
gpio_control::p1_ctrl0.setActive();
|
||||||
|
break;
|
||||||
|
case P1_Function::NotConnected:
|
||||||
|
gpio_control::p1_ctrl2.setActive();
|
||||||
|
gpio_control::p1_ctrl1.setActive();
|
||||||
|
gpio_control::p1_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P1_Function::AuxClk2:
|
||||||
|
gpio_control::p1_ctrl2.setActive();
|
||||||
|
gpio_control::p1_ctrl1.setActive();
|
||||||
|
gpio_control::p1_ctrl0.setActive();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClockManager::set_p2_control(P2_Function func) {
|
||||||
|
// Ensure all P2 control pins are configured as outputs
|
||||||
|
|
||||||
|
// Truth table based on P2_Control.csv (L=setInactive, H=setActive)
|
||||||
|
switch (func) {
|
||||||
|
case P2_Function::Clk3:
|
||||||
|
// CTRL0 is 'X' (don't care) according to CSV, we default it to Low (setInactive)
|
||||||
|
gpio_control::p2_ctrl1.setInactive();
|
||||||
|
gpio_control::p2_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P2_Function::TriggerIn:
|
||||||
|
gpio_control::p2_ctrl1.setActive();
|
||||||
|
gpio_control::p2_ctrl0.setInactive();
|
||||||
|
break;
|
||||||
|
case P2_Function::TriggerOut:
|
||||||
|
gpio_control::p2_ctrl1.setActive();
|
||||||
|
gpio_control::p2_ctrl0.setActive();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -77,6 +77,29 @@ class ClockManager {
|
|||||||
void set_reference_ppb(const int32_t ppb);
|
void set_reference_ppb(const int32_t ppb);
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
|
|
||||||
|
enum class P1_Function {
|
||||||
|
TriggerIn, // CTRL2: L, CTRL1: L, CTRL0: L
|
||||||
|
AuxClk1, // CTRL2: L, CTRL1: L, CTRL0: H
|
||||||
|
ClkIn, // CTRL2: L, CTRL1: H, CTRL0: L
|
||||||
|
TriggerOut, // CTRL2: L, CTRL1: H, CTRL0: H
|
||||||
|
P22_ClkIn, // CTRL2: H, CTRL1: L, CTRL0: L
|
||||||
|
P2_5, // CTRL2: H, CTRL1: L, CTRL0: H
|
||||||
|
NotConnected, // CTRL2: H, CTRL1: H, CTRL0: L (NC)
|
||||||
|
AuxClk2 // CTRL2: H, CTRL1: H, CTRL0: H
|
||||||
|
};
|
||||||
|
|
||||||
|
// Multiplexer 2 (P2) Functions - Based on P2_Control.csv
|
||||||
|
enum class P2_Function {
|
||||||
|
Clk3, // CTRL1: L, CTRL0: X (Don't care)
|
||||||
|
TriggerIn, // CTRL1: H, CTRL0: L
|
||||||
|
TriggerOut // CTRL1: H, CTRL0: H
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function declarations
|
||||||
|
void set_p1_control(P1_Function func);
|
||||||
|
void set_p2_control(P2_Function func);
|
||||||
|
|
||||||
uint8_t get_resampling_n() const { return _resampling_n; }
|
uint8_t get_resampling_n() const { return _resampling_n; }
|
||||||
|
|
||||||
// Si5351 diagnostic methods
|
// Si5351 diagnostic methods
|
||||||
@@ -93,6 +116,9 @@ class ClockManager {
|
|||||||
|
|
||||||
void enable_clock_output(bool enable);
|
void enable_clock_output(bool enable);
|
||||||
|
|
||||||
|
void portapack_tcxo_enable();
|
||||||
|
void portapack_tcxo_disable();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
I2C& i2c0;
|
I2C& i2c0;
|
||||||
si5351::Si5351& clock_generator;
|
si5351::Si5351& clock_generator;
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
#include "hackrf_gpio.hpp"
|
#include "hackrf_gpio.hpp"
|
||||||
#include "portapack_hal.hpp"
|
#include "portapack_hal.hpp"
|
||||||
|
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#include "gpio.hpp"
|
||||||
|
using namespace gpio_control;
|
||||||
|
|
||||||
void config_mode_blink_until_dfu();
|
void config_mode_blink_until_dfu();
|
||||||
|
|
||||||
void config_mode_set() {
|
void config_mode_set() {
|
||||||
@@ -56,7 +61,6 @@ uint32_t blink_patterns[] = {
|
|||||||
|
|
||||||
void config_mode_run() {
|
void config_mode_run() {
|
||||||
configure_pins_portapack();
|
configure_pins_portapack();
|
||||||
portapack::gpio_dfu.input();
|
|
||||||
portapack::persistent_memory::cache::init();
|
portapack::persistent_memory::cache::init();
|
||||||
|
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
@@ -75,14 +79,14 @@ void config_mode_run() {
|
|||||||
config_mode_blink_until_dfu();
|
config_mode_blink_until_dfu();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto last_dfu_btn = portapack::gpio_dfu.read();
|
auto last_dfu_btn = dfu_button.read();
|
||||||
|
|
||||||
int32_t counter = 0;
|
int32_t counter = 0;
|
||||||
int8_t blink_pattern_value = portapack::persistent_memory::config_cpld() +
|
int8_t blink_pattern_value = portapack::persistent_memory::config_cpld() +
|
||||||
(portapack::persistent_memory::config_disable_external_tcxo() ? 5 : 0);
|
(portapack::persistent_memory::config_disable_external_tcxo() ? 5 : 0);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto dfu_btn = portapack::gpio_dfu.read();
|
auto dfu_btn = dfu_button.read();
|
||||||
auto dfu_clicked = last_dfu_btn == true && dfu_btn == false;
|
auto dfu_clicked = last_dfu_btn == true && dfu_btn == false;
|
||||||
last_dfu_btn = dfu_btn;
|
last_dfu_btn = dfu_btn;
|
||||||
|
|
||||||
@@ -103,16 +107,16 @@ void config_mode_run() {
|
|||||||
|
|
||||||
auto tx_value = ((tx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
|
auto tx_value = ((tx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
|
||||||
if (tx_value) {
|
if (tx_value) {
|
||||||
hackrf::one::led_tx.on();
|
led_tx.setActive();
|
||||||
} else {
|
} else {
|
||||||
hackrf::one::led_tx.off();
|
led_tx.setInactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rx_value = ((rx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
|
auto rx_value = ((rx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
|
||||||
if (rx_value) {
|
if (rx_value) {
|
||||||
hackrf::one::led_rx.on();
|
led_rx.setActive();
|
||||||
} else {
|
} else {
|
||||||
hackrf::one::led_rx.off();
|
led_rx.setInactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
chThdSleepMilliseconds(100);
|
chThdSleepMilliseconds(100);
|
||||||
@@ -122,17 +126,17 @@ void config_mode_run() {
|
|||||||
|
|
||||||
void config_mode_blink_until_dfu() {
|
void config_mode_blink_until_dfu() {
|
||||||
while (true) {
|
while (true) {
|
||||||
hackrf::one::led_tx.on();
|
led_tx.setActive();
|
||||||
hackrf::one::led_rx.on();
|
led_rx.setActive();
|
||||||
hackrf::one::led_usb.on();
|
led_usb.setActive();
|
||||||
chThdSleepMilliseconds(10);
|
chThdSleepMilliseconds(10);
|
||||||
|
|
||||||
hackrf::one::led_tx.off();
|
led_tx.setInactive();
|
||||||
hackrf::one::led_rx.off();
|
led_rx.setInactive();
|
||||||
hackrf::one::led_usb.off();
|
led_usb.setInactive();
|
||||||
chThdSleepMilliseconds(115);
|
chThdSleepMilliseconds(115);
|
||||||
|
|
||||||
auto dfu_btn = portapack::gpio_dfu.read();
|
auto dfu_btn = dfu_button.read();
|
||||||
if (dfu_btn)
|
if (dfu_btn)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,7 @@ void config_mode_blink_until_dfu() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
chThdSleepMilliseconds(10);
|
chThdSleepMilliseconds(10);
|
||||||
|
|
||||||
auto dfu_btn = portapack::gpio_dfu.read();
|
auto dfu_btn = dfu_button.read();
|
||||||
if (!dfu_btn)
|
if (!dfu_btn)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@
|
|||||||
#include "irq_controls.hpp"
|
#include "irq_controls.hpp"
|
||||||
#include "file_path.hpp"
|
#include "file_path.hpp"
|
||||||
|
|
||||||
|
#include "gpio.hpp"
|
||||||
|
using namespace gpio_control;
|
||||||
|
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
#define DEBUG_LOG_FILE "debug_log.txt"
|
#define DEBUG_LOG_FILE "debug_log.txt"
|
||||||
@@ -134,9 +137,9 @@ void draw_line(int32_t y_offset, const char* label, regarm_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void runtime_error(uint8_t source) {
|
void runtime_error(uint8_t source) {
|
||||||
LED led = (source == CORTEX_M0) ? hackrf::one::led_rx : hackrf::one::led_tx;
|
const auto& led = (source == CORTEX_M0) ? led_rx : led_tx;
|
||||||
|
|
||||||
led.off();
|
led.setInactive();
|
||||||
|
|
||||||
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
|
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
|
||||||
while (swizzled_switches() & (1 << (int)Switch::Dfu));
|
while (swizzled_switches() & (1 << (int)Switch::Dfu));
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
|
|
||||||
#include "ch.h"
|
#include "ch.h"
|
||||||
|
|
||||||
#include "hackrf_gpio.hpp"
|
#include "gpio.hpp"
|
||||||
using namespace hackrf::one;
|
using namespace gpio_control;
|
||||||
|
|
||||||
#include "irq_rtc.hpp"
|
#include "irq_rtc.hpp"
|
||||||
|
|
||||||
@@ -185,10 +185,11 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
|||||||
|
|
||||||
SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
|
SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
|
||||||
|
|
||||||
|
power_control::vaa_power_off();
|
||||||
|
power_control::core_power_off();
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
// Power management and GPIO configuration for Praline hardware
|
// Power management and GPIO configuration for Praline hardware
|
||||||
gpio_vaa_disable.set();
|
|
||||||
gpio_1v2_enable.clear();
|
|
||||||
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
||||||
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
||||||
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
||||||
@@ -196,8 +197,6 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
|||||||
LPC_GPIO->DIR[5] &= ~(1 << 16);
|
LPC_GPIO->DIR[5] &= ~(1 << 16);
|
||||||
#else
|
#else
|
||||||
// Power management and GPIO configuration for legacy hardware
|
// Power management and GPIO configuration for legacy hardware
|
||||||
gpio_og_vaa_disable.set();
|
|
||||||
gpio_r9_vaa_disable.clear();
|
|
||||||
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
|
||||||
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
|
||||||
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
|
||||||
@@ -228,8 +227,8 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
|||||||
|
|
||||||
LPC_ADC0->CR &= ~(1 << 21);
|
LPC_ADC0->CR &= ~(1 << 21);
|
||||||
LPC_ADC1->CR &= ~(1 << 21);
|
LPC_ADC1->CR &= ~(1 << 21);
|
||||||
led_rx.off();
|
led_rx.setInactive();
|
||||||
led_usb.off();
|
led_usb.setInactive();
|
||||||
|
|
||||||
rtc_wakeup_init();
|
rtc_wakeup_init();
|
||||||
NVIC_EnableIRQ(I2C0_OR_I2C1_IRQn);
|
NVIC_EnableIRQ(I2C0_OR_I2C1_IRQn);
|
||||||
@@ -238,28 +237,29 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
|||||||
// --- Battery Status Check (I2C) ---
|
// --- Battery Status Check (I2C) ---
|
||||||
detect = battery::BatteryManagement::isDetected();
|
detect = battery::BatteryManagement::isDetected();
|
||||||
if (detect) {
|
if (detect) {
|
||||||
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
|
bool dummy;
|
||||||
|
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, dummy);
|
||||||
|
|
||||||
bool is_full = (valid_mask == 31 && percent == 100 && current <= 10) ||
|
bool is_full = (valid_mask == 31 && percent == 100 && current <= 10) ||
|
||||||
(valid_mask == 1 && percent == 100);
|
(valid_mask == 1 && percent == 100);
|
||||||
|
|
||||||
if (is_full) {
|
if (is_full) {
|
||||||
// Case 1: Battery full (All LEDs off)
|
// Case 1: Battery full (All LEDs off)
|
||||||
led_rx.off();
|
led_rx.setInactive();
|
||||||
led_tx.off();
|
led_tx.setInactive();
|
||||||
} else if ((voltage < 4150 && current < 10) || valid_mask == 0) {
|
} else if ((voltage < 4150 && current < 10) || valid_mask == 0) {
|
||||||
// Case 2: Not full but low current draw (<10mA) -> Charging error
|
// Case 2: Not full but low current draw (<10mA) -> Charging error
|
||||||
led_tx.on(); // LED indicates error/idle
|
led_tx.setActive(); // LED indicates error/idle
|
||||||
led_rx.off();
|
led_rx.setInactive();
|
||||||
} else {
|
} else {
|
||||||
// Case 3: Actively charging
|
// Case 3: Actively charging
|
||||||
led_rx.on(); // LED indicates charging
|
led_rx.setActive(); // LED indicates charging
|
||||||
led_tx.off();
|
led_tx.setInactive();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Case 4: Battery IC not detected -> Error or H2 or older, so don't show that as an error.
|
// Case 4: Battery IC not detected -> Error or H2 or older, so don't show that as an error.
|
||||||
led_tx.on();
|
led_tx.setActive();
|
||||||
led_rx.on();
|
led_rx.setActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut down I2C and power down the APB bus for sleep
|
// Shut down I2C and power down the APB bus for sleep
|
||||||
@@ -400,8 +400,6 @@ void EventDispatcher::handle_local_queue() {
|
|||||||
void EventDispatcher::handle_rtc_tick() {
|
void EventDispatcher::handle_rtc_tick() {
|
||||||
sd_card::poll_inserted();
|
sd_card::poll_inserted();
|
||||||
|
|
||||||
portapack::temperature_logger.second_tick();
|
|
||||||
|
|
||||||
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
|
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
|
||||||
if (backlight_timer.timeout_enabled()) {
|
if (backlight_timer.timeout_enabled()) {
|
||||||
if (portapack::bl_tick_counter == backlight_timer.timeout_seconds())
|
if (portapack::bl_tick_counter == backlight_timer.timeout_seconds())
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 PortaPack Mayhem
|
||||||
|
*
|
||||||
|
* 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_adsb_rx.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::adsbrx {
|
||||||
|
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<ADSBRxView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::adsbrx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_adsbrx.application_information"), used)) application_information_t _application_information_adsbrx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::adsbrx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "ADS-B",
|
||||||
|
/*.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::green().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_adsbrx */ {'P', 'A', 'D', 'R'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
Vendored
+27
-6
@@ -38,7 +38,7 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace pmem = portapack::persistent_memory;
|
namespace pmem = portapack::persistent_memory;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::adsbrx {
|
||||||
|
|
||||||
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
|
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
|
||||||
|
|
||||||
@@ -46,13 +46,19 @@ static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
|||||||
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
|
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::adsbrx
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void RecentEntriesTable<AircraftRecentEntries>::draw(
|
void RecentEntriesTable<external_app::adsbrx::AircraftRecentEntries>::draw(
|
||||||
const Entry& entry,
|
const Entry& entry,
|
||||||
const Rect& target_rect,
|
const Rect& target_rect,
|
||||||
Painter& painter,
|
Painter& painter,
|
||||||
const Style& style,
|
const Style& style,
|
||||||
RecentEntriesColumns& columns) {
|
RecentEntriesColumns& columns) {
|
||||||
|
using namespace external_app::adsbrx;
|
||||||
|
|
||||||
Color target_color;
|
Color target_color;
|
||||||
std::string entry_string;
|
std::string entry_string;
|
||||||
|
|
||||||
@@ -75,7 +81,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
|||||||
uint8_t firstcolwidth = columns.at(0).second;
|
uint8_t firstcolwidth = columns.at(0).second;
|
||||||
ipc.resize(firstcolwidth, ' '); // Make sure this is always match the first column's width that is dynamic.
|
ipc.resize(firstcolwidth, ' '); // Make sure this is always match the first column's width that is dynamic.
|
||||||
|
|
||||||
entry_string += ipc + to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
|
entry_string += ipc + to_string_dec_int((int32_t)(entry.pos.altitude / 100), 4);
|
||||||
|
|
||||||
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
|
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
|
||||||
// It is generally accepted that for every thousand feet of altitude,
|
// It is generally accepted that for every thousand feet of altitude,
|
||||||
@@ -107,6 +113,10 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
|||||||
bitmap_target, target_color, style.background);
|
bitmap_target, target_color, style.background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
namespace ui::external_app::adsbrx {
|
||||||
|
|
||||||
/* ADSBLogger ********************************************/
|
/* ADSBLogger ********************************************/
|
||||||
|
|
||||||
void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
||||||
@@ -532,6 +542,18 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
|||||||
logger = std::make_unique<ADSBLogger>();
|
logger = std::make_unique<ADSBLogger>();
|
||||||
logger->append(logs_dir / u"ADSB.TXT");
|
logger->append(logs_dir / u"ADSB.TXT");
|
||||||
|
|
||||||
|
/* First run only: start from the configuration that is known to receive
|
||||||
|
* ADS-B on this hardware -- LNA 32, VGA 32, RF amp ON. The first two are
|
||||||
|
* already ReceiverModel's defaults; the amp is not, and running without it
|
||||||
|
* costs about 14 dB, which is the difference between a busy list and an
|
||||||
|
* empty one. Once the user has saved settings for this app their choice
|
||||||
|
* wins, so this only sets the starting point.
|
||||||
|
* Going through the field rather than receiver_model keeps the displayed
|
||||||
|
* value in step: RFAmpField snapshots rf_amp() in its own constructor,
|
||||||
|
* which has already run by the time we get here. */
|
||||||
|
if (!settings_.loaded())
|
||||||
|
field_rf_amp.set_value(1);
|
||||||
|
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
baseband::set_adsb();
|
baseband::set_adsb();
|
||||||
|
|
||||||
@@ -668,9 +690,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
|||||||
(raw_data[3] & 15);
|
(raw_data[3] & 15);
|
||||||
|
|
||||||
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
|
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
|
||||||
|
// altitude = 25N - 1000 (ft); minimum is -1000 ft when N=0 (Q=1, M=0 per ICAO Annex 10).
|
||||||
altitude = 25 * n - 1000;
|
altitude = 25 * n - 1000;
|
||||||
if (altitude < 0)
|
|
||||||
altitude = 0;
|
|
||||||
} // else N is an 11 bit Gillham coded altitude
|
} // else N is an 11 bit Gillham coded altitude
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,4 +838,4 @@ void ADSBRxView::remove_expired_entries() {
|
|||||||
recent.erase(it.base(), recent.end());
|
recent.erase(it.base(), recent.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::adsbrx
|
||||||
Vendored
+2
-2
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
using namespace adsb;
|
using namespace adsb;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::adsbrx {
|
||||||
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
|
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
|
||||||
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
|
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
|
||||||
|
|
||||||
@@ -491,4 +491,4 @@ class ADSBRxView : public View {
|
|||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::adsbrx
|
||||||
+37
-29
@@ -35,7 +35,8 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace pmem = portapack::persistent_memory;
|
namespace pmem = portapack::persistent_memory;
|
||||||
|
|
||||||
namespace ais {
|
namespace ui::external_app::ais_rx {
|
||||||
|
|
||||||
namespace format {
|
namespace format {
|
||||||
|
|
||||||
static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) {
|
static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) {
|
||||||
@@ -47,7 +48,7 @@ static std::string latlon_abs_normalized(const int32_t normalized, const char su
|
|||||||
return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix;
|
return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string latlon(const Latitude latitude, const Longitude longitude) {
|
static std::string latlon(const ais::Latitude latitude, const ais::Longitude longitude) {
|
||||||
if (latitude.is_valid() && longitude.is_valid()) {
|
if (latitude.is_valid() && longitude.is_valid()) {
|
||||||
return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE");
|
return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE");
|
||||||
} else if (latitude.is_not_available() && longitude.is_not_available()) {
|
} else if (latitude.is_not_available() && longitude.is_not_available()) {
|
||||||
@@ -128,7 +129,7 @@ static std::string navigational_status(const unsigned int value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string rate_of_turn(const RateOfTurn value) {
|
static std::string rate_of_turn(const ais::RateOfTurn value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case -128:
|
case -128:
|
||||||
return "not available";
|
return "not available";
|
||||||
@@ -149,7 +150,7 @@ static std::string rate_of_turn(const RateOfTurn value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string speed_over_ground(const SpeedOverGround value) {
|
static std::string speed_over_ground(const ais::SpeedOverGround value) {
|
||||||
if (value == 1023) {
|
if (value == 1023) {
|
||||||
return "not available";
|
return "not available";
|
||||||
} else if (value == 1022) {
|
} else if (value == 1022) {
|
||||||
@@ -159,7 +160,7 @@ static std::string speed_over_ground(const SpeedOverGround value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string course_over_ground(const CourseOverGround value) {
|
static std::string course_over_ground(const ais::CourseOverGround value) {
|
||||||
if (value > 3600) {
|
if (value > 3600) {
|
||||||
return "invalid";
|
return "invalid";
|
||||||
} else if (value == 3600) {
|
} else if (value == 3600) {
|
||||||
@@ -169,7 +170,7 @@ static std::string course_over_ground(const CourseOverGround value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string true_heading(const TrueHeading value) {
|
static std::string true_heading(const ais::TrueHeading value) {
|
||||||
if (value == 511) {
|
if (value == 511) {
|
||||||
return "not available";
|
return "not available";
|
||||||
} else if (value > 359) {
|
} else if (value > 359) {
|
||||||
@@ -180,7 +181,6 @@ static std::string true_heading(const TrueHeading value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace format */
|
} /* namespace format */
|
||||||
} /* namespace ais */
|
|
||||||
|
|
||||||
void AISLogger::on_packet(const ais::Packet& packet) {
|
void AISLogger::on_packet(const ais::Packet& packet) {
|
||||||
// TODO: Unstuff here, not in baseband!
|
// TODO: Unstuff here, not in baseband!
|
||||||
@@ -265,25 +265,33 @@ void AISRecentEntry::update(const ais::Packet& packet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::ais_rx
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void RecentEntriesTable<AISRecentEntries>::draw(
|
void RecentEntriesTable<external_app::ais_rx::AISRecentEntries>::draw(
|
||||||
const Entry& entry,
|
const Entry& entry,
|
||||||
const Rect& target_rect,
|
const Rect& target_rect,
|
||||||
Painter& painter,
|
Painter& painter,
|
||||||
const Style& style,
|
const Style& style,
|
||||||
RecentEntriesColumns&) {
|
RecentEntriesColumns&) {
|
||||||
std::string line = ais::format::mmsi(entry.mmsi) + " ";
|
using namespace external_app::ais_rx;
|
||||||
|
|
||||||
|
std::string line = format::mmsi(entry.mmsi) + " ";
|
||||||
if (!entry.name.empty()) {
|
if (!entry.name.empty()) {
|
||||||
line += ais::format::text(entry.name);
|
line += format::text(entry.name);
|
||||||
} else {
|
} else {
|
||||||
line += ais::format::text(entry.call_sign);
|
line += format::text(entry.call_sign);
|
||||||
}
|
}
|
||||||
line.resize(target_rect.width() / 8, ' ');
|
line.resize(target_rect.width() / 8, ' ');
|
||||||
painter.draw_string(target_rect.location(), style, line);
|
painter.draw_string(target_rect.location(), style, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
namespace ui::external_app::ais_rx {
|
||||||
|
|
||||||
AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||||
add_children({
|
add_children({
|
||||||
&button_done,
|
&button_done,
|
||||||
@@ -298,12 +306,12 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
|||||||
|
|
||||||
button_see_map.on_select = [this, &nav](Button&) {
|
button_see_map.on_select = [this, &nav](Button&) {
|
||||||
geomap_view = nav.push<GeoMapView>(
|
geomap_view = nav.push<GeoMapView>(
|
||||||
ais::format::text(entry_.name),
|
format::text(entry_.name),
|
||||||
0,
|
0,
|
||||||
GeoPos::alt_unit::METERS,
|
GeoPos::alt_unit::METERS,
|
||||||
GeoPos::spd_unit::KNOTS,
|
GeoPos::spd_unit::KNOTS,
|
||||||
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
format::latlon_float(entry_.last_position.latitude.normalized()),
|
||||||
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
format::latlon_float(entry_.last_position.longitude.normalized()),
|
||||||
entry_.last_position.true_heading,
|
entry_.last_position.true_heading,
|
||||||
[this]() {
|
[this]() {
|
||||||
send_updates = false;
|
send_updates = false;
|
||||||
@@ -324,14 +332,14 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
|
|||||||
|
|
||||||
void AISRecentEntryDetailView::update_position() {
|
void AISRecentEntryDetailView::update_position() {
|
||||||
if (send_updates)
|
if (send_updates)
|
||||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
|
geomap_view->update_position(format::latlon_float(entry_.last_position.latitude.normalized()), format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
|
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
|
||||||
if (geomap_view && send_updates) {
|
if (geomap_view && send_updates) {
|
||||||
GeoMarker marker{};
|
GeoMarker marker{};
|
||||||
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
|
marker.lon = format::latlon_float(entry.last_position.longitude.normalized());
|
||||||
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
|
marker.lat = format::latlon_float(entry.last_position.latitude.normalized());
|
||||||
marker.angle = entry.last_position.true_heading;
|
marker.angle = entry.last_position.true_heading;
|
||||||
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
|
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
|
||||||
auto markerStored = geomap_view->store_marker(marker);
|
auto markerStored = geomap_view->store_marker(marker);
|
||||||
@@ -377,18 +385,18 @@ void AISRecentEntryDetailView::paint(Painter& painter) {
|
|||||||
|
|
||||||
auto field_rect = Rect{rect.left(), rect.top() + 16, rect.width(), 16};
|
auto field_rect = Rect{rect.left(), rect.top() + 16, rect.width(), 16};
|
||||||
|
|
||||||
field_rect = draw_field(painter, field_rect, s, "MMSI", ais::format::mmsi(entry_.mmsi));
|
field_rect = draw_field(painter, field_rect, s, "MMSI", format::mmsi(entry_.mmsi));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Ctry", ais::format::mid(entry_.mmsi));
|
field_rect = draw_field(painter, field_rect, s, "Ctry", format::mid(entry_.mmsi));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Name", ais::format::text(entry_.name));
|
field_rect = draw_field(painter, field_rect, s, "Name", format::text(entry_.name));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Call", ais::format::text(entry_.call_sign));
|
field_rect = draw_field(painter, field_rect, s, "Call", format::text(entry_.call_sign));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Dest", ais::format::text(entry_.destination));
|
field_rect = draw_field(painter, field_rect, s, "Dest", format::text(entry_.destination));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp));
|
field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Pos ", ais::format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
|
field_rect = draw_field(painter, field_rect, s, "Pos ", format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Stat", ais::format::navigational_status(entry_.navigational_status));
|
field_rect = draw_field(painter, field_rect, s, "Stat", format::navigational_status(entry_.navigational_status));
|
||||||
field_rect = draw_field(painter, field_rect, s, "RoT ", ais::format::rate_of_turn(entry_.last_position.rate_of_turn));
|
field_rect = draw_field(painter, field_rect, s, "RoT ", format::rate_of_turn(entry_.last_position.rate_of_turn));
|
||||||
field_rect = draw_field(painter, field_rect, s, "SoG ", ais::format::speed_over_ground(entry_.last_position.speed_over_ground));
|
field_rect = draw_field(painter, field_rect, s, "SoG ", format::speed_over_ground(entry_.last_position.speed_over_ground));
|
||||||
field_rect = draw_field(painter, field_rect, s, "CoG ", ais::format::course_over_ground(entry_.last_position.course_over_ground));
|
field_rect = draw_field(painter, field_rect, s, "CoG ", format::course_over_ground(entry_.last_position.course_over_ground));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Head", ais::format::true_heading(entry_.last_position.true_heading));
|
field_rect = draw_field(painter, field_rect, s, "Head", format::true_heading(entry_.last_position.true_heading));
|
||||||
field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count));
|
field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,4 +512,4 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
|
|||||||
recent_entry_detail_view.update_map_markers(recent);
|
recent_entry_detail_view.update_map_markers(recent);
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::ais_rx
|
||||||
+3
-3
@@ -50,6 +50,8 @@ using namespace lpc43xx;
|
|||||||
|
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ais_rx {
|
||||||
|
|
||||||
struct AISPosition {
|
struct AISPosition {
|
||||||
rtc::RTC timestamp{};
|
rtc::RTC timestamp{};
|
||||||
ais::Latitude latitude{};
|
ais::Latitude latitude{};
|
||||||
@@ -109,8 +111,6 @@ class AISLogger {
|
|||||||
LogFile log_file{};
|
LogFile log_file{};
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
|
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
|
||||||
|
|
||||||
class AISRecentEntryDetailView : public View {
|
class AISRecentEntryDetailView : public View {
|
||||||
@@ -239,6 +239,6 @@ class AISAppView : public View {
|
|||||||
void on_tick_second();
|
void on_tick_second();
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::ais_rx
|
||||||
|
|
||||||
#endif /*__AIS_APP_H__*/
|
#endif /*__AIS_APP_H__*/
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 PortaPack Mayhem
|
||||||
|
*
|
||||||
|
* 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 "ais_app.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ais_rx {
|
||||||
|
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<AISAppView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::ais_rx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_ais_rx.application_information"), used)) application_information_t _application_information_ais_rx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::ais_rx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "AIS Boats",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x80,
|
||||||
|
0x01,
|
||||||
|
0xC0,
|
||||||
|
0x01,
|
||||||
|
0xC0,
|
||||||
|
0x0D,
|
||||||
|
0xE0,
|
||||||
|
0x3D,
|
||||||
|
0xF0,
|
||||||
|
0x3D,
|
||||||
|
0xF8,
|
||||||
|
0x7D,
|
||||||
|
0xFC,
|
||||||
|
0x7D,
|
||||||
|
0xFC,
|
||||||
|
0x7D,
|
||||||
|
0xFE,
|
||||||
|
0x7D,
|
||||||
|
0xFF,
|
||||||
|
0x7D,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xF8,
|
||||||
|
0x7F,
|
||||||
|
0xF8,
|
||||||
|
0x3F,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_ais */ {'P', 'A', 'I', 'S'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 PortaPack Mayhem
|
||||||
|
*
|
||||||
|
* 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_aprs_rx.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::aprs_rx {
|
||||||
|
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<APRSRXView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::aprs_rx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_aprs_rx.application_information"), used)) application_information_t _application_information_aprs_rx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::aprs_rx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "APRS",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x4C,
|
||||||
|
0x32,
|
||||||
|
0xFE,
|
||||||
|
0x7F,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0xFE,
|
||||||
|
0x7F,
|
||||||
|
0x4C,
|
||||||
|
0x32,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_aprs_rx */ {'P', 'A', 'P', 'R'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
Vendored
+12
-2
@@ -31,19 +31,25 @@
|
|||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace ui::external_app::aprs_rx {
|
||||||
|
|
||||||
void APRSLogger::log_raw_data(const std::string& data) {
|
void APRSLogger::log_raw_data(const std::string& data) {
|
||||||
log_file.write_entry(data);
|
log_file.write_entry(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::aprs_rx
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void RecentEntriesTable<APRSRecentEntries>::draw(
|
void RecentEntriesTable<external_app::aprs_rx::APRSRecentEntries>::draw(
|
||||||
const Entry& entry,
|
const Entry& entry,
|
||||||
const Rect& target_rect,
|
const Rect& target_rect,
|
||||||
Painter& painter,
|
Painter& painter,
|
||||||
const Style& style,
|
const Style& style,
|
||||||
RecentEntriesColumns& columns) {
|
RecentEntriesColumns& columns) {
|
||||||
|
using namespace external_app::aprs_rx;
|
||||||
|
|
||||||
Color target_color;
|
Color target_color;
|
||||||
// auto entry_age = entry.age;
|
// auto entry_age = entry.age;
|
||||||
|
|
||||||
@@ -68,6 +74,10 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
namespace ui::external_app::aprs_rx {
|
||||||
|
|
||||||
void APRSRxView::focus() {
|
void APRSRxView::focus() {
|
||||||
options_region.focus();
|
options_region.focus();
|
||||||
}
|
}
|
||||||
@@ -402,4 +412,4 @@ void APRSRXView::focus() {
|
|||||||
|
|
||||||
APRSRXView::~APRSRXView() {
|
APRSRXView::~APRSRXView() {
|
||||||
}
|
}
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::aprs_rx
|
||||||
Vendored
+3
-3
@@ -38,6 +38,8 @@
|
|||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
#include "file_path.hpp"
|
#include "file_path.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::aprs_rx {
|
||||||
|
|
||||||
class APRSLogger {
|
class APRSLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
@@ -50,8 +52,6 @@ class APRSLogger {
|
|||||||
LogFile log_file{};
|
LogFile log_file{};
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
struct APRSRecentEntry {
|
struct APRSRecentEntry {
|
||||||
using Key = uint64_t;
|
using Key = uint64_t;
|
||||||
|
|
||||||
@@ -290,6 +290,6 @@ class APRSRXView : public View {
|
|||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::aprs_rx
|
||||||
|
|
||||||
#endif /*__UI_APRS_RX_H__*/
|
#endif /*__UI_APRS_RX_H__*/
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 PortaPack Mayhem
|
||||||
|
*
|
||||||
|
* 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_aprs_tx.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::aprs_tx {
|
||||||
|
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<APRSTXView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::aprs_tx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_aprs_tx.application_information"), used)) application_information_t _application_information_aprs_tx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::aprs_tx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "APRS TX",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x4C,
|
||||||
|
0x32,
|
||||||
|
0xFE,
|
||||||
|
0x7F,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0x25,
|
||||||
|
0xA4,
|
||||||
|
0xFE,
|
||||||
|
0x7F,
|
||||||
|
0x4C,
|
||||||
|
0x32,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
|
/*.menu_location = */ app_location_t::TX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
Vendored
+3
-2
@@ -37,7 +37,7 @@
|
|||||||
using namespace aprs;
|
using namespace aprs;
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::aprs_tx {
|
||||||
|
|
||||||
void APRSTXView::focus() {
|
void APRSTXView::focus() {
|
||||||
tx_view.focus();
|
tx_view.focus();
|
||||||
@@ -238,7 +238,8 @@ APRSTXView::APRSTXView(NavigationView& nav) {
|
|||||||
process_coordinates(lat, lon);
|
process_coordinates(lat, lon);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
|
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::aprs_tx
|
||||||
Vendored
+2
-2
@@ -33,7 +33,7 @@
|
|||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui::external_app::aprs_tx {
|
||||||
|
|
||||||
class APRSTXView : public View {
|
class APRSTXView : public View {
|
||||||
public:
|
public:
|
||||||
@@ -161,4 +161,4 @@ class APRSTXView : public View {
|
|||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} // namespace ui::external_app::aprs_tx
|
||||||
+66
-4
@@ -294,11 +294,11 @@ void BLESpamView::createWindowsPacket() {
|
|||||||
std::copy(res.begin(), res.end(), advertisementData);
|
std::copy(res.begin(), res.end(), advertisementData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLESpamView::createNameSpamPacket() {
|
// Builds a "Complete Local Name" HID advertisement for the given name.
|
||||||
const char* names[] = {"PortaHack", "PwnBt", "iSpam", "GenericFoodVagon", "SignalSnoop", "ByteBandit", "RadioRogue", "RadioRebel", "ByteBlast"};
|
// Shared by NameSpam (built-in list) and Custom (SD card list) modes.
|
||||||
|
void BLESpamView::buildNamePacket(const char* name) {
|
||||||
const char* name = names[rand() % 9]; //"PortaHack";
|
|
||||||
uint8_t name_len = strlen(name);
|
uint8_t name_len = strlen(name);
|
||||||
|
if (name_len > 19) name_len = 19; // clamp so the hex string fits advertisementData[63]
|
||||||
|
|
||||||
uint8_t size = 12 + name_len;
|
uint8_t size = 12 + name_len;
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
@@ -327,6 +327,65 @@ void BLESpamView::createNameSpamPacket() {
|
|||||||
std::copy(res.begin(), res.end(), advertisementData);
|
std::copy(res.begin(), res.end(), advertisementData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BLESpamView::createNameSpamPacket() {
|
||||||
|
const char* names[] = {"PortaHack", "PwnBt", "iSpam", "GenericFoodVagon", "SignalSnoop", "ByteBandit", "RadioRogue", "RadioRebel", "ByteBlast"};
|
||||||
|
buildNamePacket(names[rand() % 9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opens /BLESPAM/NAME.txt once and keeps it open for the session.
|
||||||
|
void BLESpamView::open_custom_file() {
|
||||||
|
custom_loaded_ = true; // attempt only once
|
||||||
|
auto open_error = custom_file_.open(u"/BLESPAM/NAME.txt");
|
||||||
|
custom_file_valid_ = !open_error; // false -> caller falls back to NameSpam
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the next line into custom_name_ (clamped to 19 chars), advancing the
|
||||||
|
// file position. At end of file it seeks back to the start to cycle. Returns
|
||||||
|
// false if the file holds no usable name. Only one line is ever held in RAM.
|
||||||
|
bool BLESpamView::read_next_custom_name() {
|
||||||
|
if (!custom_file_valid_) return false;
|
||||||
|
|
||||||
|
custom_name_.clear();
|
||||||
|
bool wrapped = false;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
auto read_result = custom_file_.read(&c, 1);
|
||||||
|
if (read_result.is_error()) return false;
|
||||||
|
|
||||||
|
if (read_result.value() == 0) {
|
||||||
|
// end of file
|
||||||
|
if (!custom_name_.empty()) break;
|
||||||
|
// use the final line
|
||||||
|
if (wrapped) return false;
|
||||||
|
// no usable name in file
|
||||||
|
if (custom_file_.seek(0).is_error()) return false;
|
||||||
|
// loop back to the first line
|
||||||
|
wrapped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
if (!custom_name_.empty()) break; // got a complete line
|
||||||
|
continue; // skip blank lines / CRLF pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_name_.size() < 19) // clamp; keep scanning rest of long line
|
||||||
|
custom_name_.push_back(c);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLESpamView::createCustomPacket() {
|
||||||
|
if (!custom_loaded_) open_custom_file();
|
||||||
|
|
||||||
|
if (!read_next_custom_name()) { // no file / unreadable / empty -> fall back
|
||||||
|
createNameSpamPacket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buildNamePacket(custom_name_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
char BLESpamView::randomNameChar() {
|
char BLESpamView::randomNameChar() {
|
||||||
int randVal = rand() % 62; // 26 uppercase + 26 lowercase + 10 digits
|
int randVal = rand() % 62; // 26 uppercase + 26 lowercase + 10 digits
|
||||||
if (randVal < 26) {
|
if (randVal < 26) {
|
||||||
@@ -622,6 +681,9 @@ void BLESpamView::createPacket(ATK_TYPE attackType) {
|
|||||||
case ATK_NAMERANDOM:
|
case ATK_NAMERANDOM:
|
||||||
createNameRandomPacket();
|
createNameRandomPacket();
|
||||||
break;
|
break;
|
||||||
|
case ATK_CUSTOM:
|
||||||
|
createCustomPacket();
|
||||||
|
break;
|
||||||
case ATK_ALL_SAFE:
|
case ATK_ALL_SAFE:
|
||||||
createAnyPacket(true);
|
createAnyPacket(true);
|
||||||
break;
|
break;
|
||||||
|
|||||||
+14
-2
@@ -39,6 +39,7 @@
|
|||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
|
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ enum ATK_TYPE {
|
|||||||
ATK_SAMSUNG,
|
ATK_SAMSUNG,
|
||||||
ATK_NAMESPAM,
|
ATK_NAMESPAM,
|
||||||
ATK_NAMERANDOM,
|
ATK_NAMERANDOM,
|
||||||
|
ATK_CUSTOM,
|
||||||
ATK_ALL_SAFE,
|
ATK_ALL_SAFE,
|
||||||
ATK_ALL
|
ATK_ALL
|
||||||
};
|
};
|
||||||
@@ -131,8 +133,9 @@ class BLESpamView : public View {
|
|||||||
{"Samsung", 4},
|
{"Samsung", 4},
|
||||||
{"NameSpam", 5},
|
{"NameSpam", 5},
|
||||||
{"NameRandom", 6},
|
{"NameRandom", 6},
|
||||||
{"All-Safe", 7},
|
{"NameCustom", 7},
|
||||||
{"All", 8}}};
|
{"All-Safe", 8},
|
||||||
|
{"All", 9}}};
|
||||||
|
|
||||||
bool is_running{false};
|
bool is_running{false};
|
||||||
|
|
||||||
@@ -144,6 +147,11 @@ class BLESpamView : public View {
|
|||||||
bool randomMac{true};
|
bool randomMac{true};
|
||||||
bool randomDev{true};
|
bool randomDev{true};
|
||||||
|
|
||||||
|
File custom_file_{}; // kept open while in Custom mode
|
||||||
|
std::string custom_name_{}; // holds only the current name (clamped)
|
||||||
|
bool custom_loaded_{false}; // true once an open attempt has been made
|
||||||
|
bool custom_file_valid_{false}; // true if /BLESPAM/NAME.txt opened successfully
|
||||||
|
|
||||||
uint8_t channel_number = 37;
|
uint8_t channel_number = 37;
|
||||||
char mac[13] = "010203040407";
|
char mac[13] = "010203040407";
|
||||||
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
|
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
|
||||||
@@ -156,7 +164,11 @@ class BLESpamView : public View {
|
|||||||
void createIosPacket(bool crash);
|
void createIosPacket(bool crash);
|
||||||
void createSamsungPacket();
|
void createSamsungPacket();
|
||||||
void createWindowsPacket();
|
void createWindowsPacket();
|
||||||
|
void buildNamePacket(const char* name); // shared name advertisement builder (NameSpam / Custom)
|
||||||
void createNameSpamPacket();
|
void createNameSpamPacket();
|
||||||
|
void createCustomPacket();
|
||||||
|
void open_custom_file(); // open /BLESPAM/NAME.txt once (one name per line)
|
||||||
|
bool read_next_custom_name(); // read next line into custom_name_, looping at EOF
|
||||||
void createNameRandomPacket();
|
void createNameRandomPacket();
|
||||||
void createAnyPacket(bool safe);
|
void createAnyPacket(bool safe);
|
||||||
void createPacket(ATK_TYPE attackType);
|
void createPacket(ATK_TYPE attackType);
|
||||||
|
|||||||
+12
-15
@@ -41,29 +41,26 @@ void BeaconUIList::paint(Painter& painter) {
|
|||||||
auto base_style = Theme::getInstance()->bg_darkest;
|
auto base_style = Theme::getInstance()->bg_darkest;
|
||||||
|
|
||||||
for (auto offset = 0u; offset < BEACON_HISTORY_SIZE; ++offset) {
|
for (auto offset = 0u; offset < BEACON_HISTORY_SIZE; ++offset) {
|
||||||
// The whole frame needs to be cleared so every line 'slot'
|
|
||||||
// is redrawn even when `text` just left empty.
|
|
||||||
auto text = std::string{};
|
|
||||||
auto index = start_index_ + offset;
|
auto index = start_index_ + offset;
|
||||||
auto line_position = rect.location() + Point{0, 1 + (int)offset * char_height};
|
|
||||||
auto is_selected = offset == selected_index_;
|
|
||||||
auto style = base_style;
|
|
||||||
|
|
||||||
if (index < db_->size()) {
|
if (index < db_->size()) {
|
||||||
|
auto line_position = rect.location() + Point{0, 1 + (int)offset * char_height};
|
||||||
|
auto is_selected = (offset == selected_index_);
|
||||||
|
auto style = base_style;
|
||||||
|
|
||||||
// Get beacon entry and format it's summary
|
// Get beacon entry and format it's summary
|
||||||
auto& entry = db_->get_beacon(index);
|
auto& entry = db_->get_beacon(index);
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
entry.formatSummary(buffer, true);
|
entry.formatSummary(buffer, true);
|
||||||
text = std::string(buffer);
|
|
||||||
|
if (index == db_->get_current_beacon_index())
|
||||||
|
// If this is the currently displayed beacon change color
|
||||||
|
style = Theme::getInstance()->bg_medium;
|
||||||
|
|
||||||
|
// Draw entry line using stack buffer directly to avoid heap allocation
|
||||||
|
painter.draw_string(
|
||||||
|
line_position, (is_selected ? style->invert() : *style), buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == db_->get_current_beacon_index())
|
|
||||||
// If this is the currently displayed beacon change color
|
|
||||||
style = Theme::getInstance()->bg_medium;
|
|
||||||
|
|
||||||
// Draw entry line
|
|
||||||
painter.draw_string(
|
|
||||||
line_position, (is_selected ? style->invert() : *style), text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a bounding rectangle when focused.
|
// Draw a bounding rectangle when focused.
|
||||||
|
|||||||
+76
-39
@@ -39,7 +39,7 @@ namespace ui::external_app::epirb_rx {
|
|||||||
|
|
||||||
// URL templates
|
// URL templates
|
||||||
#define MAPS_URL_TEMPLATE "https://www.google.com/maps/search/?api=1&query=%s%%2C%s"
|
#define MAPS_URL_TEMPLATE "https://www.google.com/maps/search/?api=1&query=%s%%2C%s"
|
||||||
#define BEACON_URL_TEMPALTE "https://decoder2.herokuapp.com/decoded/"
|
#define BEACON_URL_TEMPLATE "https://decoder2.herokuapp.com/decoded/"
|
||||||
|
|
||||||
#ifndef DISABLE_COUNTRY_CACHE
|
#ifndef DISABLE_COUNTRY_CACHE
|
||||||
int CountryManager::cache_count = 0;
|
int CountryManager::cache_count = 0;
|
||||||
@@ -71,15 +71,24 @@ void TextArea::paint(Painter& painter) {
|
|||||||
const Style& s = has_focus() ? style().invert() : style();
|
const Style& s = has_focus() ? style().invert() : style();
|
||||||
|
|
||||||
painter.fill_rectangle(rect, s.background);
|
painter.fill_rectangle(rect, s.background);
|
||||||
// We use \t as line separator since \n is used in STR_COLOR_GREEN
|
|
||||||
auto rows = split_string(content, '\t');
|
|
||||||
|
|
||||||
const int line_height = s.font.line_height();
|
const int line_height = s.font.line_height();
|
||||||
size_t line_idx = 0;
|
int line_idx = 0;
|
||||||
for (auto row : rows) {
|
|
||||||
painter.draw_string(rect.location() + Point(0, line_idx * line_height), s, row);
|
// Efficiently draw lines separated by \t without heap allocations
|
||||||
|
std::string_view sv{content};
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end;
|
||||||
|
|
||||||
|
while ((end = sv.find('\t', start)) != std::string_view::npos) {
|
||||||
|
painter.draw_string(rect.location() + Point(0, line_idx * line_height), s, sv.substr(start, end - start));
|
||||||
|
start = end + 1;
|
||||||
line_idx++;
|
line_idx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (start < sv.length()) {
|
||||||
|
painter.draw_string(rect.location() + Point(0, line_idx * line_height), s, sv.substr(start));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextArea::set_content(std::string_view value) {
|
void TextArea::set_content(std::string_view value) {
|
||||||
@@ -102,10 +111,12 @@ bool TextArea::on_key(const KeyEvent key) {
|
|||||||
void EPIRBAppView::decode_packet(const baseband::Packet& packet, Beacon& beacon) {
|
void EPIRBAppView::decode_packet(const baseband::Packet& packet, Beacon& beacon) {
|
||||||
// Convert packet bits to byte array for easier processing
|
// Convert packet bits to byte array for easier processing
|
||||||
uint8_t data[BEACON_DATA_SIZE]{};
|
uint8_t data[BEACON_DATA_SIZE]{};
|
||||||
for (size_t i = 0; i < std::min(packet.size() / 8, (size_t)BEACON_DATA_SIZE); i++) {
|
size_t max_bytes = std::min(packet.size() / 8, (size_t)BEACON_DATA_SIZE);
|
||||||
|
size_t packet_bit_idx = 0;
|
||||||
|
for (size_t i = 0; i < max_bytes; i++) {
|
||||||
uint8_t byte_val = 0;
|
uint8_t byte_val = 0;
|
||||||
for (int bit = 0; bit < 8 && (i * 8 + bit) < packet.size(); bit++) {
|
for (int bit = 0; bit < 8; bit++, packet_bit_idx++) {
|
||||||
if (packet[i * 8 + bit]) {
|
if (packet[packet_bit_idx]) {
|
||||||
byte_val |= (1 << (7 - bit));
|
byte_val |= (1 << (7 - bit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,17 +131,31 @@ void EPIRBAppView::decode_packet(const baseband::Packet& packet, Beacon& beacon)
|
|||||||
|
|
||||||
#ifdef LOGGER
|
#ifdef LOGGER
|
||||||
void EPIRBLogger::on_packet(Beacon& beacon) {
|
void EPIRBLogger::on_packet(Beacon& beacon) {
|
||||||
std::string entry = std::string(beacon.getType()) + "," +
|
std::string entry;
|
||||||
beacon.hexId + "," +
|
// Pre-allocate enough memory to avoid heap fragmentation and resizing
|
||||||
beacon.getProtocolName(); // + ",";
|
// 128 bytes should be plenty for this specific log line
|
||||||
// to_string_dec_uint(static_cast<uint8_t>(beacon.emergency_type)) + ",";
|
entry.reserve(128);
|
||||||
/*if (!beacon.location.isUnknown()) {
|
entry += beacon.getType();
|
||||||
entry += beacon.location.toString(Location::LocationFormat::DECIMAL);
|
entry += ",";
|
||||||
} else {
|
entry += beacon.hexId;
|
||||||
entry += ",";
|
entry += ",";
|
||||||
}
|
entry += beacon.getProtocolName();
|
||||||
entry += "," + beacon.country.toString() + "," +
|
|
||||||
beacon.getSatus() + "\n";*/
|
/* If you ever uncomment the rest of the logging,
|
||||||
|
* keep using the same pattern! Like this:
|
||||||
|
*
|
||||||
|
* if (!beacon.location.isUnknown()) {
|
||||||
|
* entry += beacon.location.toString(Location::LocationFormat::DECIMAL);
|
||||||
|
* } else {
|
||||||
|
* entry += ",";
|
||||||
|
* }
|
||||||
|
* entry += ",";
|
||||||
|
* entry += beacon.country.toString();
|
||||||
|
* entry += ",";
|
||||||
|
* entry += beacon.getSatus();
|
||||||
|
* entry += "\n";
|
||||||
|
*/
|
||||||
|
|
||||||
log_file.write_entry(beacon.date, entry);
|
log_file.write_entry(beacon.date, entry);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -294,12 +319,12 @@ void EPRIBQRView::set_beacon(Beacon* beacon) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EPRIBQRView::update_display() {
|
void EPRIBQRView::update_display() {
|
||||||
// Update data => we use a single TextArea component for code size optimizaton
|
// Update data => we use a single TextArea component for code size optimization
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
char* buffer_pointer = buffer;
|
char* buffer_pointer = buffer;
|
||||||
buffer_pointer += sprintf(buffer_pointer, "%sQR:%s\t\t\t\t\t\t\t\t", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
buffer_pointer += sprintf(buffer_pointer, STR_COLOR_CYAN "QR:" STR_COLOR_WHITE "\t\t\t\t\t\t\t\t");
|
||||||
if (current_beacon) {
|
if (current_beacon) {
|
||||||
buffer_pointer += sprintf(buffer_pointer, "%sData:%s\t", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
buffer_pointer += sprintf(buffer_pointer, STR_COLOR_CYAN "Data:" STR_COLOR_WHITE "\t");
|
||||||
// HEX ID 30 Hexa or HEX ID 22 Hexa bit 26 to 112
|
// HEX ID 30 Hexa or HEX ID 22 Hexa bit 26 to 112
|
||||||
buffer_pointer += current_beacon->toHexString(buffer_pointer, current_beacon->frame, true, 3, 11);
|
buffer_pointer += current_beacon->toHexString(buffer_pointer, current_beacon->frame, true, 3, 11);
|
||||||
(*(buffer_pointer++)) = '\t';
|
(*(buffer_pointer++)) = '\t';
|
||||||
@@ -320,7 +345,7 @@ void EPRIBQRView::update_qr() {
|
|||||||
if (show_map) {
|
if (show_map) {
|
||||||
// Map is selected
|
// Map is selected
|
||||||
if (!current_beacon->location.isUnknown()) {
|
if (!current_beacon->location.isUnknown()) {
|
||||||
// Loation is known => actually draw QR
|
// Location is known => actually draw QR
|
||||||
current_beacon->location.formatFloatLocation(qr_url, MAPS_URL_TEMPLATE);
|
current_beacon->location.formatFloatLocation(qr_url, MAPS_URL_TEMPLATE);
|
||||||
show_qr = true;
|
show_qr = true;
|
||||||
}
|
}
|
||||||
@@ -328,7 +353,7 @@ void EPRIBQRView::update_qr() {
|
|||||||
// Detail is selected
|
// Detail is selected
|
||||||
char* buffer_pointer = qr_url;
|
char* buffer_pointer = qr_url;
|
||||||
// Send to heroku decoder
|
// Send to heroku decoder
|
||||||
buffer_pointer += sprintf(qr_url, BEACON_URL_TEMPALTE);
|
buffer_pointer += sprintf(qr_url, BEACON_URL_TEMPLATE);
|
||||||
current_beacon->hexString(buffer_pointer, false);
|
current_beacon->hexString(buffer_pointer, false);
|
||||||
show_qr = true;
|
show_qr = true;
|
||||||
}
|
}
|
||||||
@@ -389,11 +414,10 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||||||
&view_rx,
|
&view_rx,
|
||||||
#endif
|
#endif
|
||||||
&view_qr});
|
&view_qr});
|
||||||
using option_t = std::pair<std::string, int32_t>;
|
|
||||||
using options_t = std::vector<option_t>;
|
ui::OptionsField::options_t frequ_options;
|
||||||
options_t frequ_options;
|
|
||||||
// Set frequency combo content according to FREQ.TXT file content
|
// Set frequency combo content according to FREQ.TXT file content
|
||||||
for (auto freq : ResourceManager::get_frequencies()) {
|
for (const auto& freq : ResourceManager::get_frequencies()) {
|
||||||
int32_t freq_value = atol(freq.c_str());
|
int32_t freq_value = atol(freq.c_str());
|
||||||
frequ_options.emplace_back(to_string_rounded_freq(freq_value, 3), freq_value);
|
frequ_options.emplace_back(to_string_rounded_freq(freq_value, 3), freq_value);
|
||||||
}
|
}
|
||||||
@@ -402,7 +426,7 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||||||
options_frequency.on_change = [this](size_t, ui::OptionsField::value_t v) {
|
options_frequency.on_change = [this](size_t, ui::OptionsField::value_t v) {
|
||||||
receiver_model.set_target_frequency(v);
|
receiver_model.set_target_frequency(v);
|
||||||
};
|
};
|
||||||
// Restore frequency from preferencies
|
// Restore frequency from preferences
|
||||||
options_frequency.set_by_value(receiver_model.target_frequency());
|
options_frequency.set_by_value(receiver_model.target_frequency());
|
||||||
|
|
||||||
// Tick second timer
|
// Tick second timer
|
||||||
@@ -456,6 +480,13 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||||||
audio::set_rate(audio::Rate::Hz_24000);
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
audio::output::start();
|
audio::output::start();
|
||||||
|
|
||||||
|
// Tint the channel-power bar red when the front-end overloads (ADC near
|
||||||
|
// full scale). Clipping distorts the constant-envelope carrier and the
|
||||||
|
// +/-1.1 rad biphase jumps the decoder relies on, so this cues the user to
|
||||||
|
// reduce RF-amp/LNA/VGA gain. -3 dBFS is a heuristic on the post-decimation
|
||||||
|
// level (not a literal ADC clip count); tune if it trips too early/late.
|
||||||
|
channel.set_overload_threshold(-3);
|
||||||
|
|
||||||
update_display();
|
update_display();
|
||||||
|
|
||||||
#ifdef LOGGER
|
#ifdef LOGGER
|
||||||
@@ -551,7 +582,10 @@ void EPIRBAppView::update_map() {
|
|||||||
if (!beacon.location.isUnknown()) {
|
if (!beacon.location.isUnknown()) {
|
||||||
hide_map = false;
|
hide_map = false;
|
||||||
// Set new position
|
// Set new position
|
||||||
view_map.set_main_marker(std::string(beacon.getType()) + "-" + beacon.shortId(), beacon.location.latitude.getFloatValue(), beacon.location.longitude.getFloatValue());
|
std::string tag = beacon.getType();
|
||||||
|
tag += "-";
|
||||||
|
tag += beacon.shortId();
|
||||||
|
view_map.set_main_marker(tag, beacon.location.latitude.getFloatValue(), beacon.location.longitude.getFloatValue());
|
||||||
// Add all beacons with valid locations as markers
|
// Add all beacons with valid locations as markers
|
||||||
for (size_t j = 0; j < size; j++) {
|
for (size_t j = 0; j < size; j++) {
|
||||||
if (cur_index != j) {
|
if (cur_index != j) {
|
||||||
@@ -560,8 +594,10 @@ void EPIRBAppView::update_map() {
|
|||||||
ui::GeoMarker marker;
|
ui::GeoMarker marker;
|
||||||
marker.lat = other_beacon.location.latitude.getFloatValue();
|
marker.lat = other_beacon.location.latitude.getFloatValue();
|
||||||
marker.lon = other_beacon.location.longitude.getFloatValue();
|
marker.lon = other_beacon.location.longitude.getFloatValue();
|
||||||
marker.angle = 0;
|
tag = other_beacon.getType();
|
||||||
marker.tag = std::string(other_beacon.getType()) + "-" + other_beacon.shortId();
|
tag += "-";
|
||||||
|
tag += other_beacon.shortId();
|
||||||
|
marker.tag = tag;
|
||||||
view_map.add_marker(marker);
|
view_map.add_marker(marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -580,15 +616,16 @@ void EPIRBAppView::on_tick_second() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EPIRBAppView::update_display() {
|
void EPIRBAppView::update_display() {
|
||||||
// We use a single TextArea for code size optimization
|
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
char* buffer_pointer = buffer;
|
int len = sprintf(buffer,
|
||||||
buffer_pointer += sprintf(buffer_pointer, "%sListening... Beacon:%s%2d/%d\t", STR_COLOR_CYAN, STR_COLOR_WHITE, (beacon_db.get_current_beacon_index() + 1), beacons_received);
|
STR_COLOR_CYAN "Listening... Beacon:" STR_COLOR_WHITE "%2d/%d\t" STR_COLOR_CYAN "Stats: " STR_COLOR_GREEN "%03dOK " STR_COLOR_YELLOW "%03dCOR " STR_COLOR_RED "%03dERR\t" STR_COLOR_CYAN "Current:" STR_COLOR_WHITE " ",
|
||||||
buffer_pointer += sprintf(buffer_pointer, "%sStats: %s%03dOK %s%03dCOR %s%03dERR\t", STR_COLOR_CYAN, STR_COLOR_GREEN, packets_valid, STR_COLOR_YELLOW, packets_corrected, STR_COLOR_RED, packets_error);
|
(beacon_db.get_current_beacon_index() + 1), beacons_received,
|
||||||
buffer_pointer += sprintf(buffer_pointer, "%sCurrent:%s ", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
packets_valid, packets_corrected, packets_error);
|
||||||
|
|
||||||
if (!beacon_db.empty()) {
|
if (!beacon_db.empty()) {
|
||||||
beacon_db.get_current_beacon().formatSummary(buffer_pointer, false);
|
beacon_db.get_current_beacon().formatSummary(buffer + len, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
text_status.set_content(buffer);
|
text_status.set_content(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+323
-323
@@ -1,324 +1,324 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 EPIRB Decoder Implementation
|
* Copyright (C) 2024 EPIRB Decoder Implementation
|
||||||
* Copyright (C) 2026 Frederic BORRY - ADRASEC 31
|
* Copyright (C) 2026 Frederic BORRY - ADRASEC 31
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
* any later version.
|
* any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program; see the file COPYING. If not, write to
|
* along with this program; see the file COPYING. If not, write to
|
||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
* Boston, MA 02110-1301, USA.
|
* Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __UI_EPIRB_RX_H__
|
#ifndef __UI_EPIRB_RX_H__
|
||||||
#define __UI_EPIRB_RX_H__
|
#define __UI_EPIRB_RX_H__
|
||||||
|
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_geomap.hpp"
|
#include "ui_geomap.hpp"
|
||||||
|
|
||||||
// Specan is disable to keep application size below the 32k limit
|
// Specan is disable to keep application size below the 32k limit
|
||||||
// #define SPECAN
|
// #define SPECAN
|
||||||
|
|
||||||
// Comment to disable timout reset on select and save approx 200 bytes of flash
|
// Comment to disable timout reset on select and save approx 200 bytes of flash
|
||||||
#ifndef PRALINE
|
#ifndef PRALINE
|
||||||
// Application does not fit on Praline with RESET_TIMER enabled
|
// Application does not fit on Praline with RESET_TIMER enabled
|
||||||
#define RESET_TIMER
|
#define RESET_TIMER
|
||||||
#endif
|
#endif
|
||||||
// Comment to disable squelch control
|
// Comment to disable squelch control
|
||||||
#define SQUELCH
|
#define SQUELCH
|
||||||
// Comment to disable beacon selection by encoder on detail tab
|
// Comment to disable beacon selection by encoder on detail tab
|
||||||
#define DETAIL_TAB_BEACON_SEL
|
#define DETAIL_TAB_BEACON_SEL
|
||||||
// #define LOGGER
|
// #define LOGGER
|
||||||
|
|
||||||
#ifdef SPECAN
|
#ifdef SPECAN
|
||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ui_tabview.hpp"
|
#include "ui_tabview.hpp"
|
||||||
|
|
||||||
#include "ui_qrcode.hpp"
|
#include "ui_qrcode.hpp"
|
||||||
|
|
||||||
#include "event_m0.hpp"
|
#include "event_m0.hpp"
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
|
|
||||||
#include "baseband_packet.hpp"
|
#include "baseband_packet.hpp"
|
||||||
|
|
||||||
#include "audio.hpp"
|
#include "audio.hpp"
|
||||||
|
|
||||||
#include "beacon.hpp"
|
#include "beacon.hpp"
|
||||||
#include "beacon_db.hpp"
|
#include "beacon_db.hpp"
|
||||||
#include "ui_beaconlist.hpp"
|
#include "ui_beaconlist.hpp"
|
||||||
#include "resources.hpp"
|
#include "resources.hpp"
|
||||||
|
|
||||||
namespace ui::external_app::epirb_rx {
|
namespace ui::external_app::epirb_rx {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of a packet
|
* Status of a packet
|
||||||
*/
|
*/
|
||||||
enum class PacketStatus : uint8_t {
|
enum class PacketStatus : uint8_t {
|
||||||
Valid = 0,
|
Valid = 0,
|
||||||
Corrected = 1,
|
Corrected = 1,
|
||||||
Error = 2
|
Error = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
// Position of tabs in tab view
|
// Position of tabs in tab view
|
||||||
#define EPIRB_TAB_POS_Y (UI_POS_Y(4) + 3 * 8)
|
#define EPIRB_TAB_POS_Y (UI_POS_Y(4) + 3 * 8)
|
||||||
// Height of tabs in tab view
|
// Height of tabs in tab view
|
||||||
#define EPIRB_TAB_HEIGHT (screen_height - EPIRB_TAB_POS_Y - UI_POS_HEIGHT(1))
|
#define EPIRB_TAB_HEIGHT (screen_height - EPIRB_TAB_POS_Y - UI_POS_HEIGHT(1))
|
||||||
|
|
||||||
#ifdef LOGGER
|
#ifdef LOGGER
|
||||||
class EPIRBLogger {
|
class EPIRBLogger {
|
||||||
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 on_packet(Beacon& beacon);
|
void on_packet(Beacon& beacon);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LogFile log_file{};
|
LogFile log_file{};
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dedicated TextArea component used to optimize application code size
|
* Dedicated TextArea component used to optimize application code size
|
||||||
*/
|
*/
|
||||||
class TextArea : public Widget {
|
class TextArea : public Widget {
|
||||||
public:
|
public:
|
||||||
TextArea(Rect parent_rect);
|
TextArea(Rect parent_rect);
|
||||||
|
|
||||||
#ifdef RESET_TIMER
|
#ifdef RESET_TIMER
|
||||||
std::function<void(TextArea&)> on_select{};
|
std::function<void(TextArea&)> on_select{};
|
||||||
bool on_key(const KeyEvent key) override;
|
bool on_key(const KeyEvent key) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void set_content(std::string_view value);
|
void set_content(std::string_view value);
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string content{};
|
std::string content{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class EPIRBAppView;
|
class EPIRBAppView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View for beacon detail tab
|
* View for beacon detail tab
|
||||||
*/
|
*/
|
||||||
class EPIRBDetailView : public View {
|
class EPIRBDetailView : public View {
|
||||||
public:
|
public:
|
||||||
EPIRBDetailView(Rect parent_rect, EPIRBAppView& parent);
|
EPIRBDetailView(Rect parent_rect, EPIRBAppView& parent);
|
||||||
void set_beacon(Beacon& beacon);
|
void set_beacon(Beacon& beacon);
|
||||||
#ifdef DETAIL_TAB_BEACON_SEL
|
#ifdef DETAIL_TAB_BEACON_SEL
|
||||||
bool on_encoder(EncoderEvent delta) override;
|
bool on_encoder(EncoderEvent delta) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextArea text_beacon{{UI_POS_X(0), UI_POS_Y(0), UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT}};
|
TextArea text_beacon{{UI_POS_X(0), UI_POS_Y(0), UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT}};
|
||||||
EPIRBAppView& parent_app;
|
EPIRBAppView& parent_app;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define EPIRB_RX_DEFAULT_LATITUDE 43.604f
|
#define EPIRB_RX_DEFAULT_LATITUDE 43.604f
|
||||||
#define EPIRB_RX_DEFAULT_LONGITUDE 1.458f
|
#define EPIRB_RX_DEFAULT_LONGITUDE 1.458f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View for beacon map tab
|
* View for beacon map tab
|
||||||
*/
|
*/
|
||||||
class EPIRBMapView : public View {
|
class EPIRBMapView : public View {
|
||||||
public:
|
public:
|
||||||
EPIRBMapView(Rect parent_rect);
|
EPIRBMapView(Rect parent_rect);
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
void on_show() override;
|
void on_show() override;
|
||||||
void set_main_marker(const std::string& label, float lat, float lon);
|
void set_main_marker(const std::string& label, float lat, float lon);
|
||||||
void clear_markers();
|
void clear_markers();
|
||||||
void add_marker(GeoMarker& marker);
|
void add_marker(GeoMarker& marker);
|
||||||
void hide_map(bool hide);
|
void hide_map(bool hide);
|
||||||
void repaint();
|
void repaint();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GeoMap geomap{{0, 0, UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT}};
|
GeoMap geomap{{0, 0, UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT}};
|
||||||
float lat_{EPIRB_RX_DEFAULT_LATITUDE};
|
float lat_{EPIRB_RX_DEFAULT_LATITUDE};
|
||||||
float lon_{EPIRB_RX_DEFAULT_LONGITUDE};
|
float lon_{EPIRB_RX_DEFAULT_LONGITUDE};
|
||||||
bool map_hidden{true};
|
bool map_hidden{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
#define QR_WIDTH 126
|
#define QR_WIDTH 126
|
||||||
#define QR_HEIGHT 127
|
#define QR_HEIGHT 127
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vieaw for Beacon QR tab
|
* Vieaw for Beacon QR tab
|
||||||
*/
|
*/
|
||||||
class EPRIBQRView : public View {
|
class EPRIBQRView : public View {
|
||||||
public:
|
public:
|
||||||
EPRIBQRView(Rect parent_rect);
|
EPRIBQRView(Rect parent_rect);
|
||||||
EPRIBQRView(const EPRIBQRView&) = delete;
|
EPRIBQRView(const EPRIBQRView&) = delete;
|
||||||
EPRIBQRView& operator=(const EPRIBQRView&) = delete;
|
EPRIBQRView& operator=(const EPRIBQRView&) = delete;
|
||||||
|
|
||||||
void set_beacon(Beacon* beacon);
|
void set_beacon(Beacon* beacon);
|
||||||
void update_qr();
|
void update_qr();
|
||||||
void update_display();
|
void update_display();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool show_map{true};
|
bool show_map{true};
|
||||||
Beacon* current_beacon{nullptr};
|
Beacon* current_beacon{nullptr};
|
||||||
char qr_url[128];
|
char qr_url[128];
|
||||||
|
|
||||||
OptionsField options_qr{
|
OptionsField options_qr{
|
||||||
{UI_POS_X(5), UI_POS_Y(1)},
|
{UI_POS_X(5), UI_POS_Y(1)},
|
||||||
6,
|
6,
|
||||||
{{"Map", 0},
|
{{"Map", 0},
|
||||||
{"Detail", 1}}};
|
{"Detail", 1}}};
|
||||||
|
|
||||||
QRCodeImage qr_code{
|
QRCodeImage qr_code{
|
||||||
{UI_POS_MAXWIDTH - QR_WIDTH - UI_POS_X(1), UI_POS_Y(1), QR_WIDTH, QR_HEIGHT}};
|
{UI_POS_MAXWIDTH - QR_WIDTH - UI_POS_X(1), UI_POS_Y(1), QR_WIDTH, QR_HEIGHT}};
|
||||||
|
|
||||||
TextArea text_data{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT - UI_POS_Y(1)}};
|
TextArea text_data{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT - UI_POS_Y(1)}};
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef SPECAN
|
#ifdef SPECAN
|
||||||
class EPIRBRxView : public spectrum::WaterfallView {
|
class EPIRBRxView : public spectrum::WaterfallView {
|
||||||
public:
|
public:
|
||||||
EPIRBRxView(EPIRBAppView& parent, Rect parent_rect);
|
EPIRBRxView(EPIRBAppView& parent, Rect parent_rect);
|
||||||
void on_show() override;
|
void on_show() override;
|
||||||
void on_hide() override;
|
void on_hide() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EPIRBAppView& app_view;
|
EPIRBAppView& app_view;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class EPIRBAppView final : public ui::View {
|
class EPIRBAppView final : public ui::View {
|
||||||
public:
|
public:
|
||||||
EPIRBAppView(ui::NavigationView& nav);
|
EPIRBAppView(ui::NavigationView& nav);
|
||||||
~EPIRBAppView();
|
~EPIRBAppView();
|
||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|
||||||
// Message to configure rx baseband
|
// Message to configure rx baseband
|
||||||
EPIRBRXConfig epirb_rx_config_message{};
|
EPIRBRXConfig epirb_rx_config_message{};
|
||||||
void send_config();
|
void send_config();
|
||||||
// Beacons database
|
// Beacons database
|
||||||
BeaconDB beacon_db{};
|
BeaconDB beacon_db{};
|
||||||
// Update display when beacon selection changed0
|
// Update display when beacon selection changed0
|
||||||
void on_beacon_change();
|
void on_beacon_change();
|
||||||
|
|
||||||
std::string title() const override { return "EPIRB RX"; }
|
std::string title() const override { return "EPIRB RX"; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t squelch{50};
|
uint8_t squelch{50};
|
||||||
// The delay between each frame
|
// The delay between each frame
|
||||||
uint32_t countdown{50};
|
uint32_t countdown{50};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_epirb",
|
"rx_epirb",
|
||||||
app_settings::Mode::RX,
|
app_settings::Mode::RX,
|
||||||
{
|
{
|
||||||
{"epirb_squelch"sv, &squelch},
|
{"epirb_squelch"sv, &squelch},
|
||||||
{"countdown"sv, &countdown},
|
{"countdown"sv, &countdown},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
ui::NavigationView& nav_;
|
ui::NavigationView& nav_;
|
||||||
|
|
||||||
#ifdef LOGGER
|
#ifdef LOGGER
|
||||||
std::unique_ptr<EPIRBLogger> logger{};
|
std::unique_ptr<EPIRBLogger> logger{};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
OptionsField options_frequency{
|
OptionsField options_frequency{
|
||||||
{UI_POS_X(0), UI_POS_Y(0)},
|
{UI_POS_X(0), UI_POS_Y(0)},
|
||||||
7,
|
7,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
ui::RFAmpField field_rf_amp{
|
ui::RFAmpField field_rf_amp{
|
||||||
{UI_POS_X(8), UI_POS_Y(0)}};
|
{UI_POS_X(8), UI_POS_Y(0)}};
|
||||||
|
|
||||||
ui::LNAGainField field_lna{
|
ui::LNAGainField field_lna{
|
||||||
{UI_POS_X(10), UI_POS_Y(0)}};
|
{UI_POS_X(10), UI_POS_Y(0)}};
|
||||||
|
|
||||||
ui::VGAGainField field_vga{
|
ui::VGAGainField field_vga{
|
||||||
{UI_POS_X(13), UI_POS_Y(0)}};
|
{UI_POS_X(13), UI_POS_Y(0)}};
|
||||||
|
|
||||||
ui::RSSI rssi{
|
ui::RSSI rssi{
|
||||||
{UI_POS_X(16), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(22), 4}};
|
{UI_POS_X(16), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(22), 4}};
|
||||||
|
|
||||||
ui::Channel channel{
|
ui::Channel channel{
|
||||||
{UI_POS_X(16), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(22), 4}};
|
{UI_POS_X(16), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(22), 4}};
|
||||||
|
|
||||||
// ui::Audio audio{
|
// ui::Audio audio{
|
||||||
// {UI_POS_X(16), UI_POS_Y(0) + 10, UI_POS_WIDTH_REMAINING(22), 4}};
|
// {UI_POS_X(16), UI_POS_Y(0) + 10, UI_POS_WIDTH_REMAINING(22), 4}};
|
||||||
|
|
||||||
ui::AudioVolumeField field_volume{
|
ui::AudioVolumeField field_volume{
|
||||||
{UI_POS_WIDTH_REMAINING(2), UI_POS_Y(0)}};
|
{UI_POS_WIDTH_REMAINING(2), UI_POS_Y(0)}};
|
||||||
|
|
||||||
#ifdef SQUELCH
|
#ifdef SQUELCH
|
||||||
NumberField field_squelch{
|
NumberField field_squelch{
|
||||||
{UI_POS_WIDTH_REMAINING(5), UI_POS_Y(0)},
|
{UI_POS_WIDTH_REMAINING(5), UI_POS_Y(0)},
|
||||||
2,
|
2,
|
||||||
{0, 99},
|
{0, 99},
|
||||||
1,
|
1,
|
||||||
' '};
|
' '};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Status display
|
// Status display
|
||||||
TextArea text_status{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(3)}};
|
TextArea text_status{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(3)}};
|
||||||
TextArea text_timeout{
|
TextArea text_timeout{
|
||||||
{UI_POS_X(13), UI_POS_Y(1), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}};
|
{UI_POS_X(13), UI_POS_Y(1), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}};
|
||||||
SignalToken signal_token_tick_second{};
|
SignalToken signal_token_tick_second{};
|
||||||
// Timeout string
|
// Timeout string
|
||||||
int16_t timeout{0};
|
int16_t timeout{0};
|
||||||
|
|
||||||
// Tab View
|
// Tab View
|
||||||
Rect view_rect = {0, EPIRB_TAB_POS_Y, UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT};
|
Rect view_rect = {0, EPIRB_TAB_POS_Y, UI_POS_MAXWIDTH, EPIRB_TAB_HEIGHT};
|
||||||
|
|
||||||
BeaconUIList view_list{view_rect};
|
BeaconUIList view_list{view_rect};
|
||||||
EPIRBDetailView view_detail{view_rect, (*this)};
|
EPIRBDetailView view_detail{view_rect, (*this)};
|
||||||
EPIRBMapView view_map{view_rect};
|
EPIRBMapView view_map{view_rect};
|
||||||
#ifdef SPECAN
|
#ifdef SPECAN
|
||||||
EPIRBRxView view_rx{*this, view_rect};
|
EPIRBRxView view_rx{*this, view_rect};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
EPRIBQRView view_qr{view_rect};
|
EPRIBQRView view_qr{view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"List", Theme::getInstance()->fg_cyan->foreground, &view_list},
|
{"List", Theme::getInstance()->fg_cyan->foreground, &view_list},
|
||||||
{"Detail", Theme::getInstance()->fg_green->foreground, &view_detail},
|
{"Detail", Theme::getInstance()->fg_green->foreground, &view_detail},
|
||||||
{"Map", Theme::getInstance()->fg_yellow->foreground, &view_map},
|
{"Map", Theme::getInstance()->fg_yellow->foreground, &view_map},
|
||||||
#ifdef SPECAN
|
#ifdef SPECAN
|
||||||
{"RX", Theme::getInstance()->fg_orange->foreground, &view_rx},
|
{"RX", Theme::getInstance()->fg_orange->foreground, &view_rx},
|
||||||
#endif
|
#endif
|
||||||
{"QR", Theme::getInstance()->fg_orange->foreground, &view_qr}};
|
{"QR", Theme::getInstance()->fg_orange->foreground, &view_qr}};
|
||||||
|
|
||||||
uint16_t beacons_received = 0;
|
uint16_t beacons_received = 0;
|
||||||
uint16_t packets_valid = 0;
|
uint16_t packets_valid = 0;
|
||||||
uint16_t packets_corrected = 0;
|
uint16_t packets_corrected = 0;
|
||||||
uint16_t packets_error = 0;
|
uint16_t packets_error = 0;
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
Message::ID::EPIRBPacket,
|
Message::ID::EPIRBPacket,
|
||||||
[this](Message* const p) { on_packet(p); }};
|
[this](Message* const p) { on_packet(p); }};
|
||||||
|
|
||||||
static void decode_packet(const baseband::Packet& packet, Beacon& beacon);
|
static void decode_packet(const baseband::Packet& packet, Beacon& beacon);
|
||||||
void on_packet(Message* const p);
|
void on_packet(Message* const p);
|
||||||
void update_map();
|
void update_map();
|
||||||
void on_tick_second();
|
void on_tick_second();
|
||||||
|
|
||||||
void update_display();
|
void update_display();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui::external_app::epirb_rx
|
} // namespace ui::external_app::epirb_rx
|
||||||
|
|
||||||
#endif // __UI_EPIRB_RX_H__
|
#endif // __UI_EPIRB_RX_H__
|
||||||
+198
-24
@@ -143,30 +143,6 @@ static void push_bits(uint8_t* buf, int& pos, uint64_t v, int n) {
|
|||||||
set_bit(buf, pos++, (v >> i) & 1);
|
set_bit(buf, pos++, (v >> i) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a beacon to hex string representation
|
|
||||||
* @param frame the frame to convert
|
|
||||||
* @param start true for the first half of the frame, false for the second half
|
|
||||||
* @return the hex string representation of the specieid half of the frame
|
|
||||||
*/
|
|
||||||
std::string beacon_to_hex_string(const uint8_t* frame, bool start) {
|
|
||||||
static const char hex[] = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
std::string out;
|
|
||||||
out.resize(18);
|
|
||||||
|
|
||||||
int offset = start ? 0 : 9;
|
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
|
||||||
uint8_t b = frame[offset + i];
|
|
||||||
|
|
||||||
out[i * 2] = hex[b >> 4];
|
|
||||||
out[i * 2 + 1] = hex[b & 0x0F];
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a beacon in the provided buffer
|
* Generate a beacon in the provided buffer
|
||||||
* @param frame the buffer to generate the frame in
|
* @param frame the buffer to generate the frame in
|
||||||
@@ -351,6 +327,204 @@ size_t generate_beacon(uint8_t* frame, const BeaconParams& params) {
|
|||||||
return 18;
|
return 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a beacon hex string representation (generic range)
|
||||||
|
* @param frame the frame data
|
||||||
|
* @param offset_bytes starting byte offset
|
||||||
|
* @param count_bytes number of bytes to convert
|
||||||
|
* @return the hex string representation
|
||||||
|
*/
|
||||||
|
std::string beacon_to_hex_string_range(const uint8_t* frame, int offset_bytes, int count_bytes) {
|
||||||
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
|
std::string out;
|
||||||
|
out.resize(count_bytes * 2);
|
||||||
|
for (int i = 0; i < count_bytes; i++) {
|
||||||
|
uint8_t b = frame[offset_bytes + i];
|
||||||
|
out[i * 2] = hex[b >> 4];
|
||||||
|
out[i * 2 + 1] = hex[b & 0x0F];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// SGB (Second Generation Beacon) — T.018 Rev 7, March 2021
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Reference values from T.018 canonical test vector (Appendix B.1)
|
||||||
|
#define SGB_TAC_NUMBER 230 // TAC number (16 bits, 0–65535)
|
||||||
|
#define SGB_SERIAL_NUMBER 573 // Serial number within TAC (14 bits, 0–16383)
|
||||||
|
#define SGB_HOMING_DEVICE 1 // 1 = beacon has 121.5 / 243 MHz homing device
|
||||||
|
#define SGB_RLS_FUNCTION 0 // 0 = no Return Link Service function
|
||||||
|
|
||||||
|
// BCH(250,202) generator polynomial — lower 48 bits (without leading X^48 term)
|
||||||
|
// Full g(x): X^48+X^47+X^46+X^42+X^41+X^40+X^39+X^38+X^37+X^35+X^33+X^32+X^31
|
||||||
|
// +X^26+X^24+X^23+X^22+X^20+X^19+X^18+X^17+X^16+X^13+X^12+X^11+X^10
|
||||||
|
// +X^7+X^4+X^2+X+1
|
||||||
|
// Binary MSB-first: 1110001111110101110000101110111110011110010010111 (T.018 App. B.1)
|
||||||
|
#define SGB_BCH_G_LOWER UINT64_C(0xC7EB85DF3C97)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode latitude for SGB GNSS location protocol (T.018 Appendix C)
|
||||||
|
* Pushes 23 bits: 1-bit N/S flag + 7-bit degrees + 15-bit decimal fraction
|
||||||
|
* @param frame the frame buffer
|
||||||
|
* @param pos current bit position (modified in-place)
|
||||||
|
* @param south true if southern hemisphere
|
||||||
|
* @param lat_mag absolute latitude in decimal degrees (0..90)
|
||||||
|
*/
|
||||||
|
static void push_sgb_latitude(uint8_t* frame, int& pos, bool south, float lat_mag) {
|
||||||
|
push_bits(frame, pos, south ? 1 : 0, 1);
|
||||||
|
uint32_t deg = (uint32_t)lat_mag;
|
||||||
|
if (deg > 90) deg = 90;
|
||||||
|
float frac = lat_mag - (float)deg;
|
||||||
|
uint32_t frac_n = (uint32_t)(frac * 32768.0f + 0.5f);
|
||||||
|
if (frac_n >= 32768) {
|
||||||
|
frac_n = 0;
|
||||||
|
if (deg < 90) deg++;
|
||||||
|
}
|
||||||
|
push_bits(frame, pos, deg, 7);
|
||||||
|
push_bits(frame, pos, frac_n, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode longitude for SGB GNSS location protocol (T.018 Appendix C)
|
||||||
|
* Pushes 24 bits: 1-bit E/W flag + 8-bit degrees + 15-bit decimal fraction
|
||||||
|
* @param frame the frame buffer
|
||||||
|
* @param pos current bit position (modified in-place)
|
||||||
|
* @param west true if western hemisphere
|
||||||
|
* @param lon_mag absolute longitude in decimal degrees (0..180)
|
||||||
|
*/
|
||||||
|
static void push_sgb_longitude(uint8_t* frame, int& pos, bool west, float lon_mag) {
|
||||||
|
push_bits(frame, pos, west ? 1 : 0, 1);
|
||||||
|
uint32_t deg = (uint32_t)lon_mag;
|
||||||
|
if (deg > 180) deg = 180;
|
||||||
|
float frac = lon_mag - (float)deg;
|
||||||
|
uint32_t frac_n = (uint32_t)(frac * 32768.0f + 0.5f);
|
||||||
|
if (frac_n >= 32768) {
|
||||||
|
frac_n = 0;
|
||||||
|
if (deg < 180) deg++;
|
||||||
|
}
|
||||||
|
push_bits(frame, pos, deg, 8);
|
||||||
|
push_bits(frame, pos, frac_n, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute BCH(250,202) parity using LFSR method (T.018 Appendix B.1)
|
||||||
|
* Reads 202 message bits from buffer positions 6..207 and writes
|
||||||
|
* 48 BCH parity bits to buffer positions 208..255.
|
||||||
|
* @param frame the 32-byte SGB buffer (message already written at bits 6..207)
|
||||||
|
*/
|
||||||
|
static void compute_sgb_bch(uint8_t* frame) {
|
||||||
|
uint64_t reg = 0;
|
||||||
|
for (int i = 0; i < 202; i++) {
|
||||||
|
int bp = i + 6; // buffer bit position (6 = start of message after padding)
|
||||||
|
bool msg_bit = (frame[bp >> 3] >> (7 - (bp & 7))) & 1;
|
||||||
|
bool feedback = ((reg >> 47) & 1) ^ (msg_bit ? 1u : 0u);
|
||||||
|
reg = (reg << 1) & UINT64_C(0xFFFFFFFFFFFF);
|
||||||
|
if (feedback) reg ^= SGB_BCH_G_LOWER;
|
||||||
|
}
|
||||||
|
// Append 48-bit remainder at buffer positions 208..255
|
||||||
|
int pos = 208;
|
||||||
|
push_bits(frame, pos, reg, 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map BeaconType to SGB 3-bit beacon type code (T.018 Table 3.1, bits 138-140)
|
||||||
|
* 000=ELT, 001=EPIRB, 010=PLB
|
||||||
|
*/
|
||||||
|
static uint8_t sgb_beacon_type_code(BeaconType t) {
|
||||||
|
switch (t) {
|
||||||
|
case BeaconType::EPIRB:
|
||||||
|
return 0b001;
|
||||||
|
case BeaconType::PLB:
|
||||||
|
return 0b010;
|
||||||
|
default:
|
||||||
|
case BeaconType::ELT:
|
||||||
|
return 0b000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Second Generation Beacon (SGB / T.018) frame.
|
||||||
|
* The 250-bit frame is stored in 32 bytes with 6 leading padding bits
|
||||||
|
* (bits 0..4 = 0, bit 5 = 1).
|
||||||
|
* The rotating field is Type 0 — G.008 Objective Requirements (Table 3.3),
|
||||||
|
* with elapsed_hours and time_last_loc_min updated from elapsed_s.
|
||||||
|
* @param frame 32-byte output buffer
|
||||||
|
* @param params beacon parameters
|
||||||
|
* @param elapsed_s seconds elapsed since beacon activation (drives rotating field)
|
||||||
|
* @return 32 (size of the generated frame in bytes)
|
||||||
|
*/
|
||||||
|
size_t generate_sgb_beacon(uint8_t* frame, const BeaconParams& params, uint32_t elapsed_s) {
|
||||||
|
memset(frame, 0, 32);
|
||||||
|
|
||||||
|
// 6 padding bits at start of 32-byte buffer: [0,0,0,0,1,0] (5th bit (1 based index as per specification) = 1 to indicate self-test mode)
|
||||||
|
set_bit(frame, 4, 1);
|
||||||
|
|
||||||
|
// Message bits start at buffer bit position 6 (= SGB message bit 1)
|
||||||
|
int pos = 6;
|
||||||
|
|
||||||
|
// Bits 1-16: TAC number (16 bits) — T.018 Table 3.1
|
||||||
|
push_bits(frame, pos, SGB_TAC_NUMBER, 16);
|
||||||
|
// Bits 17-30: Serial number within TAC (14 bits)
|
||||||
|
push_bits(frame, pos, SGB_SERIAL_NUMBER, 14);
|
||||||
|
// Bits 31-40: Country code (10 bits, ITU MID)
|
||||||
|
push_bits(frame, pos, params.country & 0x3FFU, 10);
|
||||||
|
// Bit 41: Status of homing device (1 = has 121.5/243 MHz homing device)
|
||||||
|
push_bits(frame, pos, params.has_121_5 ? 1 : 0, 1);
|
||||||
|
// Bit 42: RLS function flag (0 = no RLS)
|
||||||
|
push_bits(frame, pos, SGB_RLS_FUNCTION, 1);
|
||||||
|
// Bit 43: Test protocol flag
|
||||||
|
push_bits(frame, pos, params.is_test ? 1 : 0, 1);
|
||||||
|
|
||||||
|
// Bits 44-66: Encoded GNSS latitude (23 bits) — T.018 Appendix C
|
||||||
|
float lat_mag = params.location.latitude < 0.0f ? -params.location.latitude : params.location.latitude;
|
||||||
|
push_sgb_latitude(frame, pos, params.location.south, lat_mag);
|
||||||
|
// Bits 67-90: Encoded GNSS longitude (24 bits)
|
||||||
|
float lon_mag = params.location.longitude < 0.0f ? -params.location.longitude : params.location.longitude;
|
||||||
|
push_sgb_longitude(frame, pos, params.location.west, lon_mag);
|
||||||
|
|
||||||
|
// Bits 91-137: Vessel ID (47 bits = 3-bit type selector + 44-bit body)
|
||||||
|
// Type 000 = No aircraft/maritime identity; body all zeros
|
||||||
|
push_bits(frame, pos, 0, 47);
|
||||||
|
|
||||||
|
// Bits 138-140: Beacon type (3 bits)
|
||||||
|
push_bits(frame, pos, sgb_beacon_type_code(params.type), 3);
|
||||||
|
|
||||||
|
// Bits 141-154: Spare (14 bits, all 1s in normal operation)
|
||||||
|
push_bits(frame, pos, 0x3FFFU, 14);
|
||||||
|
|
||||||
|
// Bits 155-202: Rotating Field Type 0 — G.008 Objective Requirements (Table 3.3)
|
||||||
|
// Bits 155-158: Identifier (4 bits = 0000)
|
||||||
|
push_bits(frame, pos, 0b0000U, 4);
|
||||||
|
// Bits 159-164: Elapsed time since activation (6 bits, 0-63 h, truncated at 63)
|
||||||
|
uint32_t elapsed_h = elapsed_s / 3600;
|
||||||
|
if (elapsed_h > 63) elapsed_h = 63;
|
||||||
|
push_bits(frame, pos, elapsed_h, 6);
|
||||||
|
// Bits 165-175: Time from last encoded location (11 bits, 0-2046 min; 2047 = no fix)
|
||||||
|
uint32_t loc_age_min = elapsed_s / 60;
|
||||||
|
if (loc_age_min > 2046) loc_age_min = 2046;
|
||||||
|
push_bits(frame, pos, loc_age_min, 11);
|
||||||
|
// Bits 176-185: Altitude of encoded location (10 bits; 0x3FF = no altitude)
|
||||||
|
push_bits(frame, pos, 0x3FFU, 10);
|
||||||
|
// Bits 186-193: Dilution of Precision (4-bit HDOP + 4-bit VDOP; 0 = best HDOP, 0 = best VDOP)
|
||||||
|
push_bits(frame, pos, 0b00000000U, 8);
|
||||||
|
// Bits 194-195: Activation notification (00 = manual by user)
|
||||||
|
push_bits(frame, pos, 0b00U, 2);
|
||||||
|
// Bits 196-198: Remaining battery capacity (101 = >75%)
|
||||||
|
push_bits(frame, pos, 0b101U, 3);
|
||||||
|
// Bits 199-200: GNSS status (10 = 3D fix)
|
||||||
|
push_bits(frame, pos, 0b10U, 2);
|
||||||
|
// Bits 201-202: Spare (00)
|
||||||
|
push_bits(frame, pos, 0b00U, 2);
|
||||||
|
|
||||||
|
// pos == 208: 6 padding + 202 message bits consumed
|
||||||
|
|
||||||
|
// Bits 203-250: BCH(250,202) parity (48 bits) computed and written at positions 208-255
|
||||||
|
compute_sgb_bch(frame);
|
||||||
|
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui::external_app::epirb_tx
|
} // namespace ui::external_app::epirb_tx
|
||||||
|
|
||||||
#endif /*__BEACON_H__*/
|
#endif /*__BEACON_H__*/
|
||||||
+63
-14
@@ -67,12 +67,17 @@ static uint8_t hexToByte(char high, char low) {
|
|||||||
return (hexval(high) << 4) | hexval(low);
|
return (hexval(high) << 4) | hexval(low);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EPIRBTXAppView::frame_to_hex_string(bool start) {
|
std::string EPIRBTXAppView::frame_to_hex_string_range(int offset_bytes, int count_bytes) {
|
||||||
return beacon_to_hex_string(epirb_tx_message.data, start);
|
return beacon_to_hex_string_range(epirb_tx_message.data, offset_bytes, count_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPIRBTXAppView::generate_frame(BeaconParams params) {
|
void EPIRBTXAppView::generate_frame(BeaconParams params) {
|
||||||
epirb_tx_message.data_len = generate_beacon(epirb_tx_message.data, params);
|
if (format_sgb) {
|
||||||
|
uint32_t elapsed_s = sgb_start_time > 0 ? (chTimeNow() - sgb_start_time) / 1000 : 0;
|
||||||
|
epirb_tx_message.data_len = generate_sgb_beacon(epirb_tx_message.data, params, elapsed_s);
|
||||||
|
} else {
|
||||||
|
epirb_tx_message.data_len = generate_beacon(epirb_tx_message.data, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPIRBTXAppView::on_timer() {
|
void EPIRBTXAppView::on_timer() {
|
||||||
@@ -125,7 +130,7 @@ void EPIRBTXAppView::update_am_transmission() {
|
|||||||
if (am_enabled && transmitting && !transmitting_bpsk) {
|
if (am_enabled && transmitting && !transmitting_bpsk) {
|
||||||
// Start am transmission
|
// Start am transmission
|
||||||
// Restore am frequency
|
// Restore am frequency
|
||||||
epirb_tx_message.mode_bpsk = false;
|
epirb_tx_message.mode_406 = false;
|
||||||
transmitter_model.set_target_frequency(am_frequency);
|
transmitter_model.set_target_frequency(am_frequency);
|
||||||
// Send config to baseband
|
// Send config to baseband
|
||||||
baseband::set_epirb_tx_config(epirb_tx_message);
|
baseband::set_epirb_tx_config(epirb_tx_message);
|
||||||
@@ -146,10 +151,18 @@ void EPIRBTXAppView::update_frame(bool updateConfig) {
|
|||||||
file_mode_ui->text_description.set(beacon.description.substr(0, max_text_width_ext));
|
file_mode_ui->text_description.set(beacon.description.substr(0, max_text_width_ext));
|
||||||
file_mode_ui->text_description_end.set(beacon.description.size() > max_text_width_ext ? "-" + beacon.description.substr(max_text_width_ext, max_text_width_ext + max_text_width_ext - 1) : "");
|
file_mode_ui->text_description_end.set(beacon.description.size() > max_text_width_ext ? "-" + beacon.description.substr(max_text_width_ext, max_text_width_ext + max_text_width_ext - 1) : "");
|
||||||
// Udapte frame content on display
|
// Udapte frame content on display
|
||||||
text_frame.set(beacon.frame.substr(0, 18));
|
bool is_fgb = beacon.frame.size() <= BEACON_HEXA_SIZE_FGB;
|
||||||
text_frame_end.set(beacon.frame.size() > 18 ? beacon.frame.substr(18, 36) : "");
|
if (is_fgb) {
|
||||||
|
text_frame.set(beacon.frame.substr(0, BEACON_HEXA_SPLIT_FGB));
|
||||||
|
text_frame_end.set(beacon.frame.size() > BEACON_HEXA_SPLIT_FGB ? beacon.frame.substr(BEACON_HEXA_SPLIT_FGB, BEACON_HEXA_SPLIT_FGB) : "");
|
||||||
|
text_frame_sgb_end.set("");
|
||||||
|
} else {
|
||||||
|
text_frame.set(" " + beacon.frame.substr(1, BEACON_HEXA_SPLIT_SGB - 1));
|
||||||
|
text_frame_end.set(beacon.frame.size() > BEACON_HEXA_SPLIT_SGB ? beacon.frame.substr(BEACON_HEXA_SPLIT_SGB, BEACON_HEXA_SPLIT_SGB) : "");
|
||||||
|
text_frame_sgb_end.set(beacon.frame.size() > 2 * BEACON_HEXA_SPLIT_SGB ? beacon.frame.substr(2 * BEACON_HEXA_SPLIT_SGB, BEACON_HEXA_SPLIT_SGB) : "");
|
||||||
|
}
|
||||||
// Prepare tx configuration
|
// Prepare tx configuration
|
||||||
epirb_tx_message.data_len = std::min<size_t>((beacon.frame.size() / 2), 18);
|
epirb_tx_message.data_len = std::min<size_t>((beacon.frame.size() / 2), BEACON_SIZE_SGB);
|
||||||
for (uint8_t i = 0; i < epirb_tx_message.data_len; i++) {
|
for (uint8_t i = 0; i < epirb_tx_message.data_len; i++) {
|
||||||
epirb_tx_message.data[i] = hexToByte(
|
epirb_tx_message.data[i] = hexToByte(
|
||||||
beacon.frame[2 * i],
|
beacon.frame[2 * i],
|
||||||
@@ -159,8 +172,17 @@ void EPIRBTXAppView::update_frame(bool updateConfig) {
|
|||||||
// In manual mode, generate frame content for current beacon params
|
// In manual mode, generate frame content for current beacon params
|
||||||
generate_frame(beacon_params);
|
generate_frame(beacon_params);
|
||||||
// Update frame content on display
|
// Update frame content on display
|
||||||
text_frame.set(frame_to_hex_string(true));
|
if (format_sgb) {
|
||||||
text_frame_end.set(frame_to_hex_string(false));
|
// SGB: 32 bytes across 3 lines (BEACON_HEXA_SPLIT_SGB = 22 hex chars = 11 bytes)
|
||||||
|
// Remove first nibble and replace it with a space to match COSPAS hexadecimal representation specification
|
||||||
|
text_frame.set(" " + frame_to_hex_string_range(0, BEACON_HEXA_SPLIT_SGB / 2).substr(1));
|
||||||
|
text_frame_end.set(frame_to_hex_string_range(BEACON_HEXA_SPLIT_SGB / 2, BEACON_HEXA_SPLIT_SGB / 2));
|
||||||
|
text_frame_sgb_end.set(frame_to_hex_string_range(BEACON_HEXA_SPLIT_SGB, (BEACON_HEXA_SPLIT_SGB / 2) - 1));
|
||||||
|
} else {
|
||||||
|
text_frame.set(frame_to_hex_string_range(0, BEACON_HEXA_SPLIT_FGB / 2));
|
||||||
|
text_frame_end.set(frame_to_hex_string_range(BEACON_HEXA_SPLIT_FGB / 2, BEACON_HEXA_SPLIT_FGB / 2));
|
||||||
|
text_frame_sgb_end.set("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (updateConfig && send_on_change && loop) {
|
if (updateConfig && send_on_change && loop) {
|
||||||
// Need to update config / send new beacon
|
// Need to update config / send new beacon
|
||||||
@@ -176,14 +198,14 @@ void EPIRBTXAppView::update_frame(bool updateConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EPIRBTXAppView::update_config() {
|
void EPIRBTXAppView::update_config() {
|
||||||
if (!epirb_tx_message.mode_bpsk) {
|
if (!epirb_tx_message.mode_406) {
|
||||||
// Previously in AM mode => restore bpsk frequency
|
// Previously in AM mode => restore bpsk frequency
|
||||||
transmitter_model.set_target_frequency(bpsk_frequency);
|
transmitter_model.set_target_frequency(bpsk_frequency);
|
||||||
// Update displayed frequency
|
// Update displayed frequency
|
||||||
tx_view.on_show();
|
tx_view.on_show();
|
||||||
}
|
}
|
||||||
// Set mode to bpsk
|
// Set mode to bpsk
|
||||||
epirb_tx_message.mode_bpsk = true;
|
epirb_tx_message.mode_406 = true;
|
||||||
transmitting_bpsk = true;
|
transmitting_bpsk = true;
|
||||||
// Set pre/post count
|
// Set pre/post count
|
||||||
epirb_tx_message.pre_count = (160 * TONES_SAMPLERATE) / 1000; // 160 ms carrier (COSPAS spec.)
|
epirb_tx_message.pre_count = (160 * TONES_SAMPLERATE) / 1000; // 160 ms carrier (COSPAS spec.)
|
||||||
@@ -198,6 +220,13 @@ void EPIRBTXAppView::set_tx_button_state(bool active) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EPIRBTXAppView::start_tx() {
|
void EPIRBTXAppView::start_tx() {
|
||||||
|
if (format_sgb && !mode_file) {
|
||||||
|
// Track start time for the SGB rotating field elapsed-time counter
|
||||||
|
if (!transmitting) sgb_start_time = chTimeNow();
|
||||||
|
// update_frame regenerates the SGB frame with current elapsed time and refreshes display
|
||||||
|
update_frame(false);
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
last_frame_time = chTimeNow();
|
last_frame_time = chTimeNow();
|
||||||
update_config();
|
update_config();
|
||||||
loop = loop_enabled;
|
loop = loop_enabled;
|
||||||
@@ -221,7 +250,7 @@ void EPIRBTXAppView::on_tx_progress(const uint32_t progress, const bool done) {
|
|||||||
transmitting_bpsk = false;
|
transmitting_bpsk = false;
|
||||||
if (am_enabled) {
|
if (am_enabled) {
|
||||||
// BPSK frame sent, switch back to 121.5 AM signal
|
// BPSK frame sent, switch back to 121.5 AM signal
|
||||||
epirb_tx_message.mode_bpsk = false;
|
epirb_tx_message.mode_406 = false;
|
||||||
// Start am transmission
|
// Start am transmission
|
||||||
update_am_transmission();
|
update_am_transmission();
|
||||||
} else {
|
} else {
|
||||||
@@ -310,6 +339,7 @@ void EPIRBTXAppView::update_mode() {
|
|||||||
options_beacon_type.hidden(mode_file);
|
options_beacon_type.hidden(mode_file);
|
||||||
options_beacon_protocol.hidden(mode_file);
|
options_beacon_protocol.hidden(mode_file);
|
||||||
options_beacon_country.hidden(mode_file);
|
options_beacon_country.hidden(mode_file);
|
||||||
|
options_format.hidden(mode_file);
|
||||||
text_field_beacon_locator.hidden(mode_file);
|
text_field_beacon_locator.hidden(mode_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +353,7 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
&text_beacon_type,
|
&text_beacon_type,
|
||||||
&options_beacon_type,
|
&options_beacon_type,
|
||||||
&options_beacon_protocol,
|
&options_beacon_protocol,
|
||||||
|
&options_format,
|
||||||
&text_beacon_country,
|
&text_beacon_country,
|
||||||
&options_beacon_country,
|
&options_beacon_country,
|
||||||
&checkbox_beacon_internal,
|
&checkbox_beacon_internal,
|
||||||
@@ -335,6 +366,7 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
&text_field_beacon_locator,
|
&text_field_beacon_locator,
|
||||||
&text_frame,
|
&text_frame,
|
||||||
&text_frame_end,
|
&text_frame_end,
|
||||||
|
&text_frame_sgb_end,
|
||||||
&text_timeout,
|
&text_timeout,
|
||||||
&checkbox_loop,
|
&checkbox_loop,
|
||||||
&field_delay,
|
&field_delay,
|
||||||
@@ -376,6 +408,7 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
init_from_locator(beacon_params.location);
|
init_from_locator(beacon_params.location);
|
||||||
update_mode();
|
update_mode();
|
||||||
update_location();
|
update_location();
|
||||||
|
options_format.set_by_value(format_sgb ? 1 : 0);
|
||||||
|
|
||||||
options_mode.on_change = [this](size_t, OptionsField::value_t value) {
|
options_mode.on_change = [this](size_t, OptionsField::value_t value) {
|
||||||
mode_file = (((BeaconMode)value) == BeaconMode::FILE);
|
mode_file = (((BeaconMode)value) == BeaconMode::FILE);
|
||||||
@@ -405,6 +438,13 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
set_dirty();
|
set_dirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options_format.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
|
format_sgb = (v == 1);
|
||||||
|
sgb_start_time = 0; // reset elapsed counter when format changes
|
||||||
|
update_frame();
|
||||||
|
set_dirty();
|
||||||
|
};
|
||||||
|
|
||||||
options_am_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
options_am_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
bool is_real = false;
|
bool is_real = false;
|
||||||
switch ((AmChannel)v) {
|
switch ((AmChannel)v) {
|
||||||
@@ -458,6 +498,9 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
case BpskChannel::O:
|
case BpskChannel::O:
|
||||||
bpsk_frequency = BPSK_FREQUENCY_O;
|
bpsk_frequency = BPSK_FREQUENCY_O;
|
||||||
break;
|
break;
|
||||||
|
case BpskChannel::SGB:
|
||||||
|
bpsk_frequency = BPSK_FREQUENCY_SGB;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
v = (uint8_t)BpskChannel::HAM;
|
v = (uint8_t)BpskChannel::HAM;
|
||||||
// fallthrough
|
// fallthrough
|
||||||
@@ -590,6 +633,9 @@ EPIRBTXAppView::EPIRBTXAppView(
|
|||||||
case BPSK_FREQUENCY_O:
|
case BPSK_FREQUENCY_O:
|
||||||
bpsk_channel = (uint8_t)BpskChannel::O;
|
bpsk_channel = (uint8_t)BpskChannel::O;
|
||||||
break;
|
break;
|
||||||
|
case BPSK_FREQUENCY_SGB:
|
||||||
|
bpsk_channel = (uint8_t)BpskChannel::SGB;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
bpsk_channel = (uint8_t)BpskChannel::MANUAL;
|
bpsk_channel = (uint8_t)BpskChannel::MANUAL;
|
||||||
manual_bpsk_frequency = bpsk_frequency;
|
manual_bpsk_frequency = bpsk_frequency;
|
||||||
@@ -642,11 +688,14 @@ void EPIRBTXAppView::load_beacons() {
|
|||||||
Beacon beacon{};
|
Beacon beacon{};
|
||||||
beacon.title = trim(cols[0]);
|
beacon.title = trim(cols[0]);
|
||||||
beacon.description = trim(cols[1]);
|
beacon.description = trim(cols[1]);
|
||||||
// Make sure frame is not longer tha 18 bytes / 36 hex character
|
// 1G uses up to 36 hex chars, 2G uses 64 hex chars (250 bits rounded to 32 bytes).
|
||||||
beacon.frame = trim(cols[2]).substr(0, 36);
|
beacon.frame = trim(cols[2]).substr(0, BEACON_HEXA_SIZE_SGB);
|
||||||
size_t size = beacon.frame.size();
|
size_t size = beacon.frame.size();
|
||||||
if (size <= 0)
|
if (size <= 0)
|
||||||
continue; // Invalid line.
|
continue; // Invalid line.
|
||||||
|
if ((size % 2) != 0) {
|
||||||
|
beacon.frame = "0" + beacon.frame; // Pad with leading 0 to make it even length
|
||||||
|
}
|
||||||
// Beacon is valid, add it to the list
|
// Beacon is valid, add it to the list
|
||||||
beacons.emplace_back(std::move(beacon));
|
beacons.emplace_back(std::move(beacon));
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-6
@@ -33,9 +33,12 @@
|
|||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "tonesets.hpp"
|
#include "tonesets.hpp"
|
||||||
|
|
||||||
#define BEACON_HEXA_SIZE 36
|
#define BEACON_SIZE_FGB 18
|
||||||
#define BEACON_HEXA_HALF_SIZE 18
|
#define BEACON_SIZE_SGB 32
|
||||||
#define BEACON_SIZE 18
|
#define BEACON_HEXA_SIZE_FGB BEACON_SIZE_FGB * 2
|
||||||
|
#define BEACON_HEXA_SIZE_SGB BEACON_SIZE_SGB * 2
|
||||||
|
#define BEACON_HEXA_SPLIT_FGB 18
|
||||||
|
#define BEACON_HEXA_SPLIT_SGB 22
|
||||||
|
|
||||||
#define AM_TEST_FREQUENCY 121375000
|
#define AM_TEST_FREQUENCY 121375000
|
||||||
#define AM_REAL_FREQUENCY 121500000
|
#define AM_REAL_FREQUENCY 121500000
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
#define BPSK_FREQUENCY_K 406052000
|
#define BPSK_FREQUENCY_K 406052000
|
||||||
#define BPSK_FREQUENCY_N 406061000
|
#define BPSK_FREQUENCY_N 406061000
|
||||||
#define BPSK_FREQUENCY_O 406064000
|
#define BPSK_FREQUENCY_O 406064000
|
||||||
|
#define BPSK_FREQUENCY_SGB 406050000
|
||||||
|
|
||||||
namespace ui::external_app::epirb_tx {
|
namespace ui::external_app::epirb_tx {
|
||||||
|
|
||||||
@@ -85,7 +89,8 @@ enum class BpskChannel {
|
|||||||
K = 6,
|
K = 6,
|
||||||
N = 7,
|
N = 7,
|
||||||
O = 8,
|
O = 8,
|
||||||
MANUAL = 10
|
MANUAL = 10,
|
||||||
|
SGB = 100
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Location {
|
struct Location {
|
||||||
@@ -129,7 +134,7 @@ class EPIRBTXAppView : public View {
|
|||||||
void on_timer();
|
void on_timer();
|
||||||
void load_beacons();
|
void load_beacons();
|
||||||
void set_tx_button_state(bool active);
|
void set_tx_button_state(bool active);
|
||||||
std::string frame_to_hex_string(bool start);
|
std::string frame_to_hex_string_range(int offset_bytes, int count_bytes);
|
||||||
void generate_frame(BeaconParams params);
|
void generate_frame(BeaconParams params);
|
||||||
void update_frame(bool updateConfig = true);
|
void update_frame(bool updateConfig = true);
|
||||||
void update_bpsk_frequency();
|
void update_bpsk_frequency();
|
||||||
@@ -214,6 +219,7 @@ class EPIRBTXAppView : public View {
|
|||||||
{"country"sv, &beacon_country},
|
{"country"sv, &beacon_country},
|
||||||
{"internal"sv, &beacon_internal},
|
{"internal"sv, &beacon_internal},
|
||||||
{"locator"sv, &locator},
|
{"locator"sv, &locator},
|
||||||
|
{"sgb"sv, &format_sgb},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Time of the last sent frame
|
// Time of the last sent frame
|
||||||
@@ -224,6 +230,10 @@ class EPIRBTXAppView : public View {
|
|||||||
bool transmitting_bpsk{false};
|
bool transmitting_bpsk{false};
|
||||||
// True when currently looping on sending beacons
|
// True when currently looping on sending beacons
|
||||||
bool loop{false};
|
bool loop{false};
|
||||||
|
// True when SGB (Second Generation Beacon) format is selected
|
||||||
|
bool format_sgb{false};
|
||||||
|
// System time (ms) when the current SGB transmission session started
|
||||||
|
uint32_t sgb_start_time{0};
|
||||||
|
|
||||||
// Current EPIRBTXDataMessage for baseband
|
// Current EPIRBTXDataMessage for baseband
|
||||||
EPIRBTXDataMessage epirb_tx_message{};
|
EPIRBTXDataMessage epirb_tx_message{};
|
||||||
@@ -302,10 +312,17 @@ class EPIRBTXAppView : public View {
|
|||||||
{"PLB", (uint8_t)BeaconType::PLB}}};
|
{"PLB", (uint8_t)BeaconType::PLB}}};
|
||||||
OptionsField options_beacon_protocol{
|
OptionsField options_beacon_protocol{
|
||||||
{UI_POS_X(9 + 7), UI_POS_Y(1)},
|
{UI_POS_X(9 + 7), UI_POS_Y(1)},
|
||||||
30,
|
8,
|
||||||
{{"User", (uint8_t)BeaconProtocol::USER},
|
{{"User", (uint8_t)BeaconProtocol::USER},
|
||||||
{"Standard", (uint8_t)BeaconProtocol::STANDARD},
|
{"Standard", (uint8_t)BeaconProtocol::STANDARD},
|
||||||
{"National", (uint8_t)BeaconProtocol::NATIONAL}}};
|
{"National", (uint8_t)BeaconProtocol::NATIONAL}}};
|
||||||
|
// Format selector (FGB / SGB) — manual mode only, same row as type/protocol
|
||||||
|
OptionsField options_format{
|
||||||
|
{UI_POS_X_RIGHT(4), UI_POS_Y(1)},
|
||||||
|
4,
|
||||||
|
{{"FGB", 0},
|
||||||
|
{"SGB", 1}}};
|
||||||
|
|
||||||
OptionsField options_beacon_country{
|
OptionsField options_beacon_country{
|
||||||
{UI_POS_X(9), UI_POS_Y(2)},
|
{UI_POS_X(9), UI_POS_Y(2)},
|
||||||
7,
|
7,
|
||||||
@@ -337,6 +354,9 @@ class EPIRBTXAppView : public View {
|
|||||||
Text text_frame_end{
|
Text text_frame_end{
|
||||||
{UI_POS_X(6), UI_POS_Y(7), UI_POS_WIDTH_REMAINING(6), UI_POS_DEFAULT_HEIGHT},
|
{UI_POS_X(6), UI_POS_Y(7), UI_POS_WIDTH_REMAINING(6), UI_POS_DEFAULT_HEIGHT},
|
||||||
""};
|
""};
|
||||||
|
Text text_frame_sgb_end{
|
||||||
|
{UI_POS_X(6), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(6), UI_POS_DEFAULT_HEIGHT},
|
||||||
|
""};
|
||||||
|
|
||||||
Text text_timeout{
|
Text text_timeout{
|
||||||
{UI_POS_X(14), UI_POS_Y(10), UI_POS_WIDTH(2), UI_POS_DEFAULT_HEIGHT},
|
{UI_POS_X(14), UI_POS_Y(10), UI_POS_WIDTH(2), UI_POS_DEFAULT_HEIGHT},
|
||||||
@@ -389,6 +409,7 @@ class EPIRBTXAppView : public View {
|
|||||||
{"406.052 MHz (K)", (uint8_t)BpskChannel::K},
|
{"406.052 MHz (K)", (uint8_t)BpskChannel::K},
|
||||||
{"406.061 MHz (N)", (uint8_t)BpskChannel::N},
|
{"406.061 MHz (N)", (uint8_t)BpskChannel::N},
|
||||||
{"406.064 MHz (O)", (uint8_t)BpskChannel::O},
|
{"406.064 MHz (O)", (uint8_t)BpskChannel::O},
|
||||||
|
{"406.050 MHz (SGB)", (uint8_t)BpskChannel::SGB},
|
||||||
{"Manual", (uint8_t)BpskChannel::MANUAL}}};
|
{"Manual", (uint8_t)BpskChannel::MANUAL}}};
|
||||||
|
|
||||||
// Transmitter view
|
// Transmitter view
|
||||||
|
|||||||
+60
-11
@@ -133,6 +133,14 @@ set(EXTCPPSRC
|
|||||||
external/noaaapt_rx/main.cpp
|
external/noaaapt_rx/main.cpp
|
||||||
external/noaaapt_rx/ui_noaaapt_rx.cpp
|
external/noaaapt_rx/ui_noaaapt_rx.cpp
|
||||||
|
|
||||||
|
#vor_rx
|
||||||
|
external/vor_rx/main.cpp
|
||||||
|
external/vor_rx/ui_vor_rx.cpp
|
||||||
|
|
||||||
|
#vor_tx
|
||||||
|
external/vor_tx/main.cpp
|
||||||
|
external/vor_tx/ui_vor_tx.cpp
|
||||||
|
|
||||||
#shoppingcart_lock 272 bytes
|
#shoppingcart_lock 272 bytes
|
||||||
external/shoppingcart_lock/main.cpp
|
external/shoppingcart_lock/main.cpp
|
||||||
external/shoppingcart_lock/shoppingcart_lock.cpp
|
external/shoppingcart_lock/shoppingcart_lock.cpp
|
||||||
@@ -161,6 +169,7 @@ set(EXTCPPSRC
|
|||||||
#mcu_temperature 112
|
#mcu_temperature 112
|
||||||
external/mcu_temperature/main.cpp
|
external/mcu_temperature/main.cpp
|
||||||
external/mcu_temperature/mcu_temperature.cpp
|
external/mcu_temperature/mcu_temperature.cpp
|
||||||
|
external/mcu_temperature/temperature_logger.cpp
|
||||||
|
|
||||||
#fmradio 640
|
#fmradio 640
|
||||||
external/fmradio/main.cpp
|
external/fmradio/main.cpp
|
||||||
@@ -327,7 +336,7 @@ set(EXTCPPSRC
|
|||||||
external/rtty_tx/ui_rtty_tx.cpp
|
external/rtty_tx/ui_rtty_tx.cpp
|
||||||
external/rtty_tx/baudot.cpp
|
external/rtty_tx/baudot.cpp
|
||||||
|
|
||||||
#pocsag_tx
|
#pocsag_tx
|
||||||
external/pocsag_tx/main.cpp
|
external/pocsag_tx/main.cpp
|
||||||
external/pocsag_tx/ui_pocsag_tx.cpp
|
external/pocsag_tx/ui_pocsag_tx.cpp
|
||||||
|
|
||||||
@@ -362,6 +371,46 @@ set(EXTCPPSRC
|
|||||||
#hard_reset
|
#hard_reset
|
||||||
external/hard_reset/main.cpp
|
external/hard_reset/main.cpp
|
||||||
external/hard_reset/ui_hard_reset.cpp
|
external/hard_reset/ui_hard_reset.cpp
|
||||||
|
|
||||||
|
#secplustx
|
||||||
|
external/secplustx/main.cpp
|
||||||
|
external/secplustx/ui_secplustx.cpp
|
||||||
|
external/secplustx/secplustx.cpp
|
||||||
|
|
||||||
|
#signal_hunter
|
||||||
|
external/signal_hunter/main.cpp
|
||||||
|
external/signal_hunter/ui_signal_hunter.cpp
|
||||||
|
|
||||||
|
|
||||||
|
#tetra rx
|
||||||
|
external/tetra_rx/main.cpp
|
||||||
|
external/tetra_rx/ui_tetra_rx.cpp
|
||||||
|
external/tetra_rx/tetra_crc.cpp
|
||||||
|
external/tetra_rx/tetra_descrambler.cpp
|
||||||
|
external/tetra_rx/tetra_interleave.cpp
|
||||||
|
external/tetra_rx/tetra_rcpc.cpp
|
||||||
|
external/tetra_rx/tetra_viterbi.cpp
|
||||||
|
|
||||||
|
#adsb rx
|
||||||
|
external/adsbrx/main.cpp
|
||||||
|
external/adsbrx/ui_adsb_rx.cpp
|
||||||
|
|
||||||
|
#ais rx
|
||||||
|
external/ais_rx/main.cpp
|
||||||
|
external/ais_rx/ais_app.cpp
|
||||||
|
|
||||||
|
#aprs rx
|
||||||
|
external/aprs_rx/main.cpp
|
||||||
|
external/aprs_rx/ui_aprs_rx.cpp
|
||||||
|
|
||||||
|
#aprs tx
|
||||||
|
external/aprs_tx/main.cpp
|
||||||
|
external/aprs_tx/ui_aprs_tx.cpp
|
||||||
|
|
||||||
|
#sd over usb
|
||||||
|
external/sdusb/main.cpp
|
||||||
|
external/sdusb/ui_sd_over_usb.cpp
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(EXTAPPLIST
|
set(EXTAPPLIST
|
||||||
@@ -396,6 +445,8 @@ set(EXTAPPLIST
|
|||||||
acars_rx
|
acars_rx
|
||||||
wefax_rx
|
wefax_rx
|
||||||
noaaapt_rx
|
noaaapt_rx
|
||||||
|
vor_rx
|
||||||
|
vor_tx
|
||||||
shoppingcart_lock
|
shoppingcart_lock
|
||||||
ookbrute
|
ookbrute
|
||||||
ook_editor
|
ook_editor
|
||||||
@@ -451,14 +502,12 @@ set(EXTAPPLIST
|
|||||||
two_tone_pager
|
two_tone_pager
|
||||||
two_tone_rx
|
two_tone_rx
|
||||||
hard_reset
|
hard_reset
|
||||||
|
secplustx
|
||||||
|
signal_hunter
|
||||||
|
tetra_rx
|
||||||
|
adsbrx
|
||||||
|
ais_rx
|
||||||
|
aprs_rx
|
||||||
|
aprs_tx
|
||||||
|
sdusb
|
||||||
)
|
)
|
||||||
|
|
||||||
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
|
|
||||||
if(NOT BOARD STREQUAL "PRALINE")
|
|
||||||
list(APPEND EXTCPPSRC
|
|
||||||
external/sdusb/main.cpp
|
|
||||||
external/sdusb/ui_sd_over_usb.cpp
|
|
||||||
)
|
|
||||||
list(APPEND EXTAPPLIST sdusb)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|||||||
+153
-87
@@ -110,6 +110,15 @@ MEMORY
|
|||||||
ram_external_app_two_tone_rx (rwx) : org = 0xAE050000, len = 32k
|
ram_external_app_two_tone_rx (rwx) : org = 0xAE050000, len = 32k
|
||||||
ram_external_app_flex_tx (rwx) : org = 0xAE060000, len = 32k
|
ram_external_app_flex_tx (rwx) : org = 0xAE060000, len = 32k
|
||||||
ram_external_app_hard_reset (rwx) : org = 0xAE070000, len = 32k
|
ram_external_app_hard_reset (rwx) : org = 0xAE070000, len = 32k
|
||||||
|
ram_external_app_secplustx (rwx) : org = 0xAE080000, len = 32k
|
||||||
|
ram_external_app_vor_rx (rwx) : org = 0xAE090000, len = 32k
|
||||||
|
ram_external_app_vor_tx (rwx) : org = 0xAE0A0000, len = 32k
|
||||||
|
ram_external_app_signal_hunter (rwx) : org = 0xAE0B0000, len = 32k
|
||||||
|
ram_external_app_tetra_rx (rwx) : org = 0xAE0C0000, len = 32k
|
||||||
|
ram_external_app_adsbrx (rwx) : org = 0xAE0D0000, len = 32k
|
||||||
|
ram_external_app_ais_rx (rwx) : org = 0xAE0E0000, len = 32k
|
||||||
|
ram_external_app_aprs_rx (rwx) : org = 0xAE0F0000, len = 32k
|
||||||
|
ram_external_app_aprs_tx (rwx) : org = 0xAE100000, len = 32k
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTIONS
|
SECTIONS
|
||||||
@@ -117,522 +126,579 @@ SECTIONS
|
|||||||
.external_app_afsk_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_afsk_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_afsk_rx.application_information));
|
KEEP(*(.external_app.app_afsk_rx.application_information));
|
||||||
*(*ui*external_app*afsk_rx*);
|
*/external/afsk_rx/*(*ui*external_app*afsk_rx*);
|
||||||
} > ram_external_app_afsk_rx
|
} > ram_external_app_afsk_rx
|
||||||
|
|
||||||
.external_app_calculator : ALIGN(4) SUBALIGN(4)
|
.external_app_calculator : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_calculator.application_information));
|
KEEP(*(.external_app.app_calculator.application_information));
|
||||||
*(*ui*external_app*calculator*);
|
*/external/calculator/*(*ui*external_app*calculator*);
|
||||||
} > ram_external_app_calculator
|
} > ram_external_app_calculator
|
||||||
|
|
||||||
.external_app_font_viewer : ALIGN(4) SUBALIGN(4)
|
.external_app_font_viewer : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_font_viewer.application_information));
|
KEEP(*(.external_app.app_font_viewer.application_information));
|
||||||
*(*ui*external_app*font_viewer*);
|
*/external/font_viewer/*(*ui*external_app*font_viewer*);
|
||||||
} > ram_external_app_font_viewer
|
} > ram_external_app_font_viewer
|
||||||
|
|
||||||
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_blespam.application_information));
|
KEEP(*(.external_app.app_blespam.application_information));
|
||||||
*(*ui*external_app*blespam*);
|
*/external/blespam/*(*ui*external_app*blespam*);
|
||||||
} > ram_external_app_blespam
|
} > ram_external_app_blespam
|
||||||
|
|
||||||
.external_app_analogtv : ALIGN(4) SUBALIGN(4)
|
.external_app_analogtv : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_analogtv.application_information));
|
KEEP(*(.external_app.app_analogtv.application_information));
|
||||||
*(*ui*external_app*analogtv*);
|
*/external/analogtv/*(*ui*external_app*analogtv*);
|
||||||
} > ram_external_app_analogtv
|
} > ram_external_app_analogtv
|
||||||
|
|
||||||
.external_app_nrf_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_nrf_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_nrf_rx.application_information));
|
KEEP(*(.external_app.app_nrf_rx.application_information));
|
||||||
*(*ui*external_app*nrf_rx*);
|
*/external/nrf_rx/*(*ui*external_app*nrf_rx*);
|
||||||
} > ram_external_app_nrf_rx
|
} > ram_external_app_nrf_rx
|
||||||
|
|
||||||
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
|
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_coasterp.application_information));
|
KEEP(*(.external_app.app_coasterp.application_information));
|
||||||
*(*ui*external_app*coasterp*);
|
*/external/coasterp/*(*ui*external_app*coasterp*);
|
||||||
} > ram_external_app_coasterp
|
} > ram_external_app_coasterp
|
||||||
|
|
||||||
.external_app_lge : ALIGN(4) SUBALIGN(4)
|
.external_app_lge : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_lge.application_information));
|
KEEP(*(.external_app.app_lge.application_information));
|
||||||
*(*ui*external_app*lge*);
|
*/external/lge/*(*ui*external_app*lge*);
|
||||||
} > ram_external_app_lge
|
} > ram_external_app_lge
|
||||||
|
|
||||||
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_lcr.application_information));
|
KEEP(*(.external_app.app_lcr.application_information));
|
||||||
*(*ui*external_app*lcr*);
|
*/external/lcr/*(*ui*external_app*lcr*);
|
||||||
} > ram_external_app_lcr
|
} > ram_external_app_lcr
|
||||||
|
|
||||||
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_jammer.application_information));
|
KEEP(*(.external_app.app_jammer.application_information));
|
||||||
*(*ui*external_app*jammer*);
|
*/external/jammer/*(*ui*external_app*jammer*);
|
||||||
} > ram_external_app_jammer
|
} > ram_external_app_jammer
|
||||||
|
|
||||||
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
|
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_gpssim.application_information));
|
KEEP(*(.external_app.app_gpssim.application_information));
|
||||||
*(*ui*external_app*gpssim*);
|
*/external/gpssim/*(*ui*external_app*gpssim*);
|
||||||
} > ram_external_app_gpssim
|
} > ram_external_app_gpssim
|
||||||
|
|
||||||
.external_app_spainter : ALIGN(4) SUBALIGN(4)
|
.external_app_spainter : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_spainter.application_information));
|
KEEP(*(.external_app.app_spainter.application_information));
|
||||||
*(*ui*external_app*spainter*);
|
*/external/spainter/*(*ui*external_app*spainter*);
|
||||||
} > ram_external_app_spainter
|
} > ram_external_app_spainter
|
||||||
|
|
||||||
.external_app_keyfob : ALIGN(4) SUBALIGN(4)
|
.external_app_keyfob : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_keyfob.application_information));
|
KEEP(*(.external_app.app_keyfob.application_information));
|
||||||
*(*ui*external_app*keyfob*);
|
*/external/keyfob/*(*ui*external_app*keyfob*);
|
||||||
} > ram_external_app_keyfob
|
} > ram_external_app_keyfob
|
||||||
|
|
||||||
.external_app_tetris : ALIGN(4) SUBALIGN(4)
|
.external_app_tetris : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_tetris.application_information));
|
KEEP(*(.external_app.app_tetris.application_information));
|
||||||
*(*ui*external_app*tetris*);
|
*/external/tetris/*(*ui*external_app*tetris*);
|
||||||
} > ram_external_app_tetris
|
} > ram_external_app_tetris
|
||||||
|
|
||||||
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
|
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_extsensors.application_information));
|
KEEP(*(.external_app.app_extsensors.application_information));
|
||||||
*(*ui*external_app*extsensors*);
|
*/external/extsensors/*(*ui*external_app*extsensors*);
|
||||||
} > ram_external_app_extsensors
|
} > ram_external_app_extsensors
|
||||||
|
|
||||||
.external_app_foxhunt_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_foxhunt_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_foxhunt_rx.application_information));
|
KEEP(*(.external_app.app_foxhunt_rx.application_information));
|
||||||
*(*ui*external_app*foxhunt_rx*);
|
*/external/foxhunt/*(*ui*external_app*foxhunt_rx*);
|
||||||
} > ram_external_app_foxhunt_rx
|
} > ram_external_app_foxhunt_rx
|
||||||
|
|
||||||
.external_app_audio_test : ALIGN(4) SUBALIGN(4)
|
.external_app_audio_test : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_audio_test.application_information));
|
KEEP(*(.external_app.app_audio_test.application_information));
|
||||||
*(*ui*external_app*audio_test*);
|
*/external/audio_test/*(*ui*external_app*audio_test*);
|
||||||
} > ram_external_app_audio_test
|
} > ram_external_app_audio_test
|
||||||
|
|
||||||
.external_app_wardrivemap : ALIGN(4) SUBALIGN(4)
|
.external_app_wardrivemap : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_wardrivemap.application_information));
|
KEEP(*(.external_app.app_wardrivemap.application_information));
|
||||||
*(*ui*external_app*wardrivemap*);
|
*/external/wardrivemap/*(*ui*external_app*wardrivemap*);
|
||||||
} > ram_external_app_wardrivemap
|
} > ram_external_app_wardrivemap
|
||||||
|
|
||||||
.external_app_tpmsrx : ALIGN(4) SUBALIGN(4)
|
.external_app_tpmsrx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_tpmsrx.application_information));
|
KEEP(*(.external_app.app_tpmsrx.application_information));
|
||||||
*(*ui*external_app*tpmsrx*);
|
*/external/tpmsrx/*(*ui*external_app*tpmsrx*);
|
||||||
} > ram_external_app_tpmsrx
|
} > ram_external_app_tpmsrx
|
||||||
|
|
||||||
.external_app_tpmstx : ALIGN(4) SUBALIGN(4)
|
.external_app_tpmstx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_tpmstx.application_information));
|
KEEP(*(.external_app.app_tpmstx.application_information));
|
||||||
*(*ui*external_app*tpmstx*);
|
*/external/tpmstx/*(*ui*external_app*tpmstx*);
|
||||||
} > ram_external_app_tpmstx
|
} > ram_external_app_tpmstx
|
||||||
|
|
||||||
.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));
|
||||||
*(*ui*external_app*protoview*);
|
*/external/protoview/*(*ui*external_app*protoview*);
|
||||||
} > ram_external_app_protoview
|
} > ram_external_app_protoview
|
||||||
|
|
||||||
.external_app_adsbtx : ALIGN(4) SUBALIGN(4)
|
.external_app_adsbtx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_adsbtx.application_information));
|
KEEP(*(.external_app.app_adsbtx.application_information));
|
||||||
*(*ui*external_app*adsbtx*);
|
*/external/adsbtx/*(*ui*external_app*adsbtx*);
|
||||||
} > ram_external_app_adsbtx
|
} > ram_external_app_adsbtx
|
||||||
|
|
||||||
.external_app_morse_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_morse_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_morse_tx.application_information));
|
KEEP(*(.external_app.app_morse_tx.application_information));
|
||||||
*(*ui*external_app*morse_tx*);
|
*/external/morse_tx/*(*ui*external_app*morse_tx*);
|
||||||
} > ram_external_app_morse_tx
|
} > ram_external_app_morse_tx
|
||||||
|
|
||||||
.external_app_sstvtx : ALIGN(4) SUBALIGN(4)
|
.external_app_sstvtx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_sstvtx.application_information));
|
KEEP(*(.external_app.app_sstvtx.application_information));
|
||||||
*(*ui*external_app*sstvtx*);
|
*/external/sstvtx/*(*ui*external_app*sstvtx*);
|
||||||
} > ram_external_app_sstvtx
|
} > ram_external_app_sstvtx
|
||||||
|
|
||||||
.external_app_random_password : ALIGN(4) SUBALIGN(4)
|
.external_app_random_password : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_random_password.application_information));
|
KEEP(*(.external_app.app_random_password.application_information));
|
||||||
*(*ui*external_app*random_password*);
|
*/external/random_password/*(*ui*external_app*random_password*);
|
||||||
} > ram_external_app_random_password
|
} > ram_external_app_random_password
|
||||||
|
|
||||||
.external_app_acars_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_acars_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_acars_rx.application_information));
|
KEEP(*(.external_app.app_acars_rx.application_information));
|
||||||
*(*ui*external_app*acars_rx*);
|
*/external/acars_rx/*(*ui*external_app*acars_rx*);
|
||||||
} > ram_external_app_acars_rx
|
} > ram_external_app_acars_rx
|
||||||
|
|
||||||
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
|
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
|
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
|
||||||
*(*ui*external_app*shoppingcart_lock*);
|
*/external/shoppingcart_lock/*(*ui*external_app*shoppingcart_lock*);
|
||||||
} > ram_external_app_shoppingcart_lock
|
} > ram_external_app_shoppingcart_lock
|
||||||
|
|
||||||
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
|
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_cvs_spam.application_information));
|
KEEP(*(.external_app.app_cvs_spam.application_information));
|
||||||
*(*ui*external_app*cvs_spam*);
|
*/external/cvs_spam/*(*ui*external_app*cvs_spam*);
|
||||||
} > ram_external_app_cvs_spam
|
} > ram_external_app_cvs_spam
|
||||||
|
|
||||||
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
|
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_ookbrute.application_information));
|
KEEP(*(.external_app.app_ookbrute.application_information));
|
||||||
*(*ui*external_app*ookbrute*);
|
*/external/ookbrute/*(*ui*external_app*ookbrute*);
|
||||||
} > ram_external_app_ookbrute
|
} > ram_external_app_ookbrute
|
||||||
|
|
||||||
.external_app_ook_editor : ALIGN(4) SUBALIGN(4)
|
.external_app_ook_editor : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_ook_editor.application_information));
|
KEEP(*(.external_app.app_ook_editor.application_information));
|
||||||
*(*ui*external_app*ook_editor*);
|
*/external/ook_editor/*(*ui*external_app*ook_editor*);
|
||||||
} > ram_external_app_ook_editor
|
} > ram_external_app_ook_editor
|
||||||
|
|
||||||
.external_app_flippertx : ALIGN(4) SUBALIGN(4)
|
.external_app_flippertx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_flippertx.application_information));
|
KEEP(*(.external_app.app_flippertx.application_information));
|
||||||
*(*ui*external_app*flippertx*);
|
*/external/flippertx/*(*ui*external_app*flippertx*);
|
||||||
} > ram_external_app_flippertx
|
} > ram_external_app_flippertx
|
||||||
|
|
||||||
.external_app_remote : ALIGN(4) SUBALIGN(4)
|
.external_app_remote : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_remote.application_information));
|
KEEP(*(.external_app.app_remote.application_information));
|
||||||
*(*ui*external_app*remote*);
|
*/external/remote/*(*ui*external_app*remote*);
|
||||||
} > ram_external_app_remote
|
} > ram_external_app_remote
|
||||||
|
|
||||||
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
|
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
||||||
*(*ui*external_app*mcu_temperature*);
|
*/external/mcu_temperature/*(*ui*external_app*mcu_temperature*);
|
||||||
} > ram_external_app_mcu_temperature
|
} > ram_external_app_mcu_temperature
|
||||||
|
|
||||||
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_fmradio.application_information));
|
KEEP(*(.external_app.app_fmradio.application_information));
|
||||||
*(*ui*external_app*fmradio*);
|
*/external/fmradio/*(*ui*external_app*fmradio*);
|
||||||
} > ram_external_app_fmradio
|
} > ram_external_app_fmradio
|
||||||
|
|
||||||
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_tuner.application_information));
|
KEEP(*(.external_app.app_tuner.application_information));
|
||||||
*(*ui*external_app*tuner*);
|
*/external/tuner/*(*ui*external_app*tuner*);
|
||||||
} > ram_external_app_tuner
|
} > ram_external_app_tuner
|
||||||
|
|
||||||
.external_app_metronome : ALIGN(4) SUBALIGN(4)
|
.external_app_metronome : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_metronome.application_information));
|
KEEP(*(.external_app.app_metronome.application_information));
|
||||||
*(*ui*external_app*metronome*);
|
*/external/metronome/*(*ui*external_app*metronome*);
|
||||||
} > ram_external_app_metronome
|
} > ram_external_app_metronome
|
||||||
|
|
||||||
.external_app_app_manager : ALIGN(4) SUBALIGN(4)
|
.external_app_app_manager : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_app_manager.application_information));
|
KEEP(*(.external_app.app_app_manager.application_information));
|
||||||
*(*ui*external_app*app_manager*);
|
*/external/app_manager/*(*ui*external_app*app_manager*);
|
||||||
} > ram_external_app_app_manager
|
} > ram_external_app_app_manager
|
||||||
|
|
||||||
.external_app_hopper : ALIGN(4) SUBALIGN(4)
|
.external_app_hopper : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_hopper.application_information));
|
KEEP(*(.external_app.app_hopper.application_information));
|
||||||
*(*ui*external_app*hopper*);
|
*/external/hopper/*(*ui*external_app*hopper*);
|
||||||
} > ram_external_app_hopper
|
} > ram_external_app_hopper
|
||||||
|
|
||||||
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
|
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_antenna_length.application_information));
|
KEEP(*(.external_app.app_antenna_length.application_information));
|
||||||
*(*ui*external_app*antenna_length*);
|
*/external/antenna_length/*(*ui*external_app*antenna_length*);
|
||||||
} > ram_external_app_antenna_length
|
} > ram_external_app_antenna_length
|
||||||
|
|
||||||
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
|
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_view_wav.application_information));
|
KEEP(*(.external_app.app_view_wav.application_information));
|
||||||
*(*ui*external_app*view_wav*);
|
*/external/wav_view/*(*ui*external_app*view_wav*);
|
||||||
} > ram_external_app_view_wav
|
} > ram_external_app_view_wav
|
||||||
|
|
||||||
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
|
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_sd_wipe.application_information));
|
KEEP(*(.external_app.app_sd_wipe.application_information));
|
||||||
*(*ui*external_app*sd_wipe*);
|
*/external/sd_wipe/*(*ui*external_app*sd_wipe*);
|
||||||
} > ram_external_app_sd_wipe
|
} > ram_external_app_sd_wipe
|
||||||
|
|
||||||
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
|
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_playlist_editor.application_information));
|
KEEP(*(.external_app.app_playlist_editor.application_information));
|
||||||
*(*ui*external_app*playlist_editor*);
|
*/external/playlist_editor/*(*ui*external_app*playlist_editor*);
|
||||||
} > ram_external_app_playlist_editor
|
} > ram_external_app_playlist_editor
|
||||||
|
|
||||||
.external_app_snake : ALIGN(4) SUBALIGN(4)
|
.external_app_snake : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_snake.application_information));
|
KEEP(*(.external_app.app_snake.application_information));
|
||||||
*(*ui*external_app*snake*);
|
*/external/snake/*(*ui*external_app*snake*);
|
||||||
} > ram_external_app_snake
|
} > ram_external_app_snake
|
||||||
|
|
||||||
.external_app_stopwatch : ALIGN(4) SUBALIGN(4)
|
.external_app_stopwatch : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_stopwatch.application_information));
|
KEEP(*(.external_app.app_stopwatch.application_information));
|
||||||
*(*ui*external_app*stopwatch*);
|
*/external/stopwatch/*(*ui*external_app*stopwatch*);
|
||||||
} > ram_external_app_stopwatch
|
} > ram_external_app_stopwatch
|
||||||
|
|
||||||
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_wefax_rx.application_information));
|
KEEP(*(.external_app.app_wefax_rx.application_information));
|
||||||
*(*ui*external_app*wefax_rx*);
|
*/external/wefax_rx/*(*ui*external_app*wefax_rx*);
|
||||||
} > ram_external_app_wefax_rx
|
} > ram_external_app_wefax_rx
|
||||||
|
|
||||||
.external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_noaaapt_rx.application_information));
|
KEEP(*(.external_app.app_noaaapt_rx.application_information));
|
||||||
*(*ui*external_app*noaaapt_rx*);
|
*/external/noaaapt_rx/*(*ui*external_app*noaaapt_rx*);
|
||||||
} > ram_external_app_noaaapt_rx
|
} > ram_external_app_noaaapt_rx
|
||||||
|
|
||||||
|
.external_app_vor_rx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_vor_rx.application_information));
|
||||||
|
*/external/vor_rx/*(*ui*external_app*vor_rx*);
|
||||||
|
} > ram_external_app_vor_rx
|
||||||
|
|
||||||
|
.external_app_vor_tx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_vor_tx.application_information));
|
||||||
|
*/external/vor_tx/*(*ui*external_app*vor_tx*);
|
||||||
|
} > ram_external_app_vor_tx
|
||||||
|
|
||||||
.external_app_breakout : ALIGN(4) SUBALIGN(4)
|
.external_app_breakout : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_breakout.application_information));
|
KEEP(*(.external_app.app_breakout.application_information));
|
||||||
*(*ui*external_app*breakout*);
|
*/external/breakout/*(*ui*external_app*breakout*);
|
||||||
} > ram_external_app_breakout
|
} > ram_external_app_breakout
|
||||||
|
|
||||||
.external_app_doom : ALIGN(4) SUBALIGN(4)
|
.external_app_doom : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_doom.application_information));
|
KEEP(*(.external_app.app_doom.application_information));
|
||||||
*(*ui*external_app*doom*);
|
*/external/doom/*(*ui*external_app*doom*);
|
||||||
} > ram_external_app_doom
|
} > ram_external_app_doom
|
||||||
|
|
||||||
.external_app_debug_pmem : ALIGN(4) SUBALIGN(4)
|
.external_app_debug_pmem : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_debug_pmem.application_information));
|
KEEP(*(.external_app.app_debug_pmem.application_information));
|
||||||
*(*ui*external_app*debug_pmem*);
|
*/external/debug_pmem/*(*ui*external_app*debug_pmem*);
|
||||||
} > ram_external_app_debug_pmem
|
} > ram_external_app_debug_pmem
|
||||||
|
|
||||||
.external_app_scanner : ALIGN(4) SUBALIGN(4)
|
.external_app_scanner : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_scanner.application_information));
|
KEEP(*(.external_app.app_scanner.application_information));
|
||||||
*(*ui*external_app*scanner*);
|
*/external/scanner/*(*ui*external_app*scanner*);
|
||||||
} > ram_external_app_scanner
|
} > ram_external_app_scanner
|
||||||
|
|
||||||
.external_app_level : ALIGN(4) SUBALIGN(4)
|
.external_app_level : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_level.application_information));
|
KEEP(*(.external_app.app_level.application_information));
|
||||||
*(*ui*external_app*level*);
|
*/external/level/*(*ui*external_app*level*);
|
||||||
} > ram_external_app_level
|
} > ram_external_app_level
|
||||||
|
|
||||||
.external_app_gfxeq : ALIGN(4) SUBALIGN(4)
|
.external_app_gfxeq : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_gfxeq.application_information));
|
KEEP(*(.external_app.app_gfxeq.application_information));
|
||||||
*(*ui*external_app*gfxeq*);
|
*/external/gfxeq/*(*ui*external_app*gfxeq*);
|
||||||
} > ram_external_app_gfxeq
|
} > ram_external_app_gfxeq
|
||||||
|
|
||||||
.external_app_waterfall_designer : ALIGN(4) SUBALIGN(4)
|
.external_app_waterfall_designer : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_waterfall_designer.application_information));
|
KEEP(*(.external_app.app_waterfall_designer.application_information));
|
||||||
*(*ui*external_app*waterfall_designer*);
|
*/external/waterfall_designer/*(*ui*external_app*waterfall_designer*);
|
||||||
} > ram_external_app_waterfall_designer
|
} > ram_external_app_waterfall_designer
|
||||||
|
|
||||||
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_detector_rx.application_information));
|
KEEP(*(.external_app.app_detector_rx.application_information));
|
||||||
*(*ui*external_app*detector_rx*);
|
*/external/detector_rx/*(*ui*external_app*detector_rx*);
|
||||||
} > ram_external_app_detector_rx
|
} > ram_external_app_detector_rx
|
||||||
|
|
||||||
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
|
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_dinogame.application_information));
|
KEEP(*(.external_app.app_dinogame.application_information));
|
||||||
*(*ui*external_app*dinogame*);
|
*/external/dinogame/*(*ui*external_app*dinogame*);
|
||||||
} > ram_external_app_dinogame
|
} > ram_external_app_dinogame
|
||||||
|
|
||||||
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
|
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_spaceinv.application_information));
|
KEEP(*(.external_app.app_spaceinv.application_information));
|
||||||
*(*ui*external_app*spaceinv*);
|
*/external/spaceinv/*(*ui*external_app*spaceinv*);
|
||||||
} > ram_external_app_spaceinv
|
} > ram_external_app_spaceinv
|
||||||
|
|
||||||
.external_app_blackjack : ALIGN(4) SUBALIGN(4)
|
.external_app_blackjack : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_blackjack.application_information));
|
KEEP(*(.external_app.app_blackjack.application_information));
|
||||||
*(*ui*external_app*blackjack*);
|
*/external/blackjack/*(*ui*external_app*blackjack*);
|
||||||
} > ram_external_app_blackjack
|
} > ram_external_app_blackjack
|
||||||
|
|
||||||
.external_app_battleship : ALIGN(4) SUBALIGN(4)
|
.external_app_battleship : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_battleship.application_information));
|
KEEP(*(.external_app.app_battleship.application_information));
|
||||||
*(*ui*external_app*battleship*);
|
*/external/battleship/*(*ui*external_app*battleship*);
|
||||||
} > ram_external_app_battleship
|
} > ram_external_app_battleship
|
||||||
|
|
||||||
.external_app_ert : ALIGN(4) SUBALIGN(4)
|
.external_app_ert : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_ert.application_information));
|
KEEP(*(.external_app.app_ert.application_information));
|
||||||
*(*ui*external_app*ert*);
|
*/external/ert/*(*ui*external_app*ert*);
|
||||||
} > ram_external_app_ert
|
} > ram_external_app_ert
|
||||||
|
|
||||||
.external_app_epirb_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_epirb_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_epirb_rx.application_information));
|
KEEP(*(.external_app.app_epirb_rx.application_information));
|
||||||
*(*ui*external_app*epirb_rx*);
|
*/external/epirb_rx/*(*ui*external_app*epirb_rx*);
|
||||||
} > ram_external_app_epirb_rx
|
} > ram_external_app_epirb_rx
|
||||||
|
|
||||||
.external_app_soundboard : ALIGN(4) SUBALIGN(4)
|
.external_app_soundboard : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_soundboard.application_information));
|
KEEP(*(.external_app.app_soundboard.application_information));
|
||||||
*(*ui*external_app*soundboard*);
|
*/external/soundboard/*(*ui*external_app*soundboard*);
|
||||||
} > ram_external_app_soundboard
|
} > ram_external_app_soundboard
|
||||||
|
|
||||||
.external_app_game2048 : ALIGN(4) SUBALIGN(4)
|
.external_app_game2048 : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_game2048.application_information));
|
KEEP(*(.external_app.app_game2048.application_information));
|
||||||
*(*ui*external_app*game2048*);
|
*/external/game2048/*(*ui*external_app*game2048*);
|
||||||
} > ram_external_app_game2048
|
} > ram_external_app_game2048
|
||||||
|
|
||||||
.external_app_bht_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_bht_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_bht_tx.application_information));
|
KEEP(*(.external_app.app_bht_tx.application_information));
|
||||||
*(*ui*external_app*bht_tx*);
|
*/external/bht_tx/*(*ui*external_app*bht_tx*);
|
||||||
} > ram_external_app_bht_tx
|
} > ram_external_app_bht_tx
|
||||||
|
|
||||||
.external_app_morse_practice : ALIGN(4) SUBALIGN(4)
|
.external_app_morse_practice : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_morse_practice.application_information));
|
KEEP(*(.external_app.app_morse_practice.application_information));
|
||||||
*(*ui*external_app*morse_practice*);
|
*/external/morse_practice/*(*ui*external_app*morse_practice*);
|
||||||
} > ram_external_app_morse_practice
|
} > ram_external_app_morse_practice
|
||||||
|
|
||||||
.external_app_adult_toys_controller : ALIGN(4) SUBALIGN(4)
|
.external_app_adult_toys_controller : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_adult_toys_controller.application_information));
|
KEEP(*(.external_app.app_adult_toys_controller.application_information));
|
||||||
*(*ui*external_app*adult_toys_controller*);
|
*/external/adult_toys_controller/*(*ui*external_app*adult_toys_controller*);
|
||||||
} > ram_external_app_adult_toys_controller
|
} > ram_external_app_adult_toys_controller
|
||||||
|
|
||||||
.external_app_flex_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_flex_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_flex_rx.application_information));
|
KEEP(*(.external_app.app_flex_rx.application_information));
|
||||||
*(*ui*external_app*flex_rx*);
|
*/external/flex_rx/*(*ui*external_app*flex_rx*);
|
||||||
} > ram_external_app_flex_rx
|
} > ram_external_app_flex_rx
|
||||||
|
|
||||||
.external_app_sstvrx : ALIGN(4) SUBALIGN(4)
|
.external_app_sstvrx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_sstvrx.application_information));
|
KEEP(*(.external_app.app_sstvrx.application_information));
|
||||||
*(*ui*external_app*sstvrx*);
|
*/external/sstvrx/*(*ui*external_app*sstvrx*);
|
||||||
} > ram_external_app_sstvrx
|
} > ram_external_app_sstvrx
|
||||||
|
|
||||||
.external_app_subcarrx : ALIGN(4) SUBALIGN(4)
|
.external_app_subcarrx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_subcarrx.application_information));
|
KEEP(*(.external_app.app_subcarrx.application_information));
|
||||||
*(*ui*external_app*subcarrx*);
|
*/external/subcarrx/*(*ui*external_app*subcarrx*);
|
||||||
} > ram_external_app_subcarrx
|
} > ram_external_app_subcarrx
|
||||||
|
|
||||||
.external_app_siggen : ALIGN(4) SUBALIGN(4)
|
.external_app_siggen : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_siggen.application_information));
|
KEEP(*(.external_app.app_siggen.application_information));
|
||||||
*(*ui*external_app*siggen*);
|
*/external/siggen/*(*ui*external_app*siggen*);
|
||||||
} > ram_external_app_siggen
|
} > ram_external_app_siggen
|
||||||
|
|
||||||
.external_app_sdusb : ALIGN(4) SUBALIGN(4)
|
.external_app_sdusb : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_sdusb.application_information));
|
KEEP(*(.external_app.app_sdusb.application_information));
|
||||||
*(*ui*external_app*sdusb*);
|
*/external/sdusb/*(*ui*external_app*sdusb*);
|
||||||
} > ram_external_app_sdusb
|
} > ram_external_app_sdusb
|
||||||
|
|
||||||
.external_app_morse_radio : ALIGN(4) SUBALIGN(4)
|
.external_app_morse_radio : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_morse_radio.application_information));
|
KEEP(*(.external_app.app_morse_radio.application_information));
|
||||||
*(*ui*external_app*morse_radio*);
|
*/external/morse_radio/*(*ui*external_app*morse_radio*);
|
||||||
} > ram_external_app_morse_radio
|
} > ram_external_app_morse_radio
|
||||||
|
|
||||||
.external_app_morseradiotx : ALIGN(4) SUBALIGN(4)
|
.external_app_morseradiotx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_morseradiotx.application_information));
|
KEEP(*(.external_app.app_morseradiotx.application_information));
|
||||||
*(*ui*external_app*morseradiotx*);
|
*/external/morseradiotx/*(*ui*external_app*morseradiotx*);
|
||||||
} > ram_external_app_morseradiotx
|
} > ram_external_app_morseradiotx
|
||||||
|
|
||||||
.external_app_keeloqtx : ALIGN(4) SUBALIGN(4)
|
.external_app_keeloqtx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_keeloqtx.application_information));
|
KEEP(*(.external_app.app_keeloqtx.application_information));
|
||||||
*(*ui*external_app*keeloqtx*);
|
*/external/keeloqtx/*(*ui*external_app*keeloqtx*);
|
||||||
} > ram_external_app_keeloqtx
|
} > ram_external_app_keeloqtx
|
||||||
|
|
||||||
.external_app_rtty_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_rtty_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_rtty_rx.application_information));
|
KEEP(*(.external_app.app_rtty_rx.application_information));
|
||||||
*(*ui*external_app*rtty_rx*);
|
*/external/rtty_rx/*(*ui*external_app*rtty_rx*);
|
||||||
} > ram_external_app_rtty_rx
|
} > ram_external_app_rtty_rx
|
||||||
|
|
||||||
.external_app_rtty_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_rtty_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_rtty_tx.application_information));
|
KEEP(*(.external_app.app_rtty_tx.application_information));
|
||||||
*(*ui*external_app*rtty_tx*);
|
*/external/rtty_tx/*(*ui*external_app*rtty_tx*);
|
||||||
} > ram_external_app_rtty_tx
|
} > ram_external_app_rtty_tx
|
||||||
|
|
||||||
.external_app_pocsag_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_pocsag_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_pocsag_tx.application_information));
|
KEEP(*(.external_app.app_pocsag_tx.application_information));
|
||||||
*(*ui*external_app*pocsag_tx*);
|
*/external/pocsag_tx/*(*ui*external_app*pocsag_tx*);
|
||||||
} > ram_external_app_pocsag_tx
|
} > ram_external_app_pocsag_tx
|
||||||
|
|
||||||
.external_app_time_sink : ALIGN(4) SUBALIGN(4)
|
.external_app_time_sink : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_time_sink.application_information));
|
KEEP(*(.external_app.app_time_sink.application_information));
|
||||||
*(*ui*external_app*time_sink*);
|
*/external/time_sink/*(*ui*external_app*time_sink*);
|
||||||
} > ram_external_app_time_sink
|
} > ram_external_app_time_sink
|
||||||
|
|
||||||
.external_app_same_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_same_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_same_tx.application_information));
|
KEEP(*(.external_app.app_same_tx.application_information));
|
||||||
*(*ui*external_app*same_tx*);
|
*/external/same_tx/*(*ui*external_app*same_tx*);
|
||||||
} > ram_external_app_same_tx
|
} > ram_external_app_same_tx
|
||||||
|
|
||||||
.external_app_kiss_tnc : ALIGN(4) SUBALIGN(4)
|
.external_app_kiss_tnc : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_kiss_tnc.application_information));
|
KEEP(*(.external_app.app_kiss_tnc.application_information));
|
||||||
*(*ui*external_app*kiss_tnc*);
|
*/external/kiss_tnc/*(*ui*external_app*kiss_tnc*);
|
||||||
} > ram_external_app_kiss_tnc
|
} > ram_external_app_kiss_tnc
|
||||||
|
|
||||||
.external_app_mdc_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_mdc_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_mdc_tx.application_information));
|
KEEP(*(.external_app.app_mdc_tx.application_information));
|
||||||
*(*ui*external_app*mdc_tx*);
|
*/external/mdc_tx/*(*ui*external_app*mdc_tx*);
|
||||||
} > ram_external_app_mdc_tx
|
} > ram_external_app_mdc_tx
|
||||||
|
|
||||||
.external_app_epirb_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_epirb_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_epirb_tx.application_information));
|
KEEP(*(.external_app.app_epirb_tx.application_information));
|
||||||
*(*ui*external_app*epirb_tx*);
|
*/external/epirb_tx/*(*ui*external_app*epirb_tx*);
|
||||||
} > ram_external_app_epirb_tx
|
} > ram_external_app_epirb_tx
|
||||||
|
|
||||||
.external_app_fpv_detect : ALIGN(4) SUBALIGN(4)
|
.external_app_fpv_detect : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_fpv_detect.application_information));
|
KEEP(*(.external_app.app_fpv_detect.application_information));
|
||||||
*(*ui*external_app*fpv_detect*);
|
*/external/fpv_detect/*(*ui*external_app*fpv_detect*);
|
||||||
} > ram_external_app_fpv_detect
|
} > ram_external_app_fpv_detect
|
||||||
|
|
||||||
.external_app_p25_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_p25_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_p25_tx.application_information));
|
KEEP(*(.external_app.app_p25_tx.application_information));
|
||||||
*(*ui*external_app*p25_tx*);
|
*/external/p25_tx/*(*ui*external_app*p25_tx*);
|
||||||
} > ram_external_app_p25_tx
|
} > ram_external_app_p25_tx
|
||||||
|
|
||||||
.external_app_two_tone_pager : ALIGN(4) SUBALIGN(4)
|
.external_app_two_tone_pager : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_two_tone_pager.application_information));
|
KEEP(*(.external_app.app_two_tone_pager.application_information));
|
||||||
*(*ui*external_app*two_tone_pager*);
|
*/external/two_tone_pager/*(*ui*external_app*two_tone_pager*);
|
||||||
} > ram_external_app_two_tone_pager
|
} > ram_external_app_two_tone_pager
|
||||||
|
|
||||||
.external_app_two_tone_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_two_tone_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_two_tone_rx.application_information));
|
KEEP(*(.external_app.app_two_tone_rx.application_information));
|
||||||
*(*ui*external_app*two_tone_rx*);
|
*/external/two_tone_rx/*(*ui*external_app*two_tone_rx*);
|
||||||
} > ram_external_app_two_tone_rx
|
} > ram_external_app_two_tone_rx
|
||||||
|
|
||||||
.external_app_flex_tx : ALIGN(4) SUBALIGN(4)
|
.external_app_flex_tx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_flex_tx.application_information));
|
KEEP(*(.external_app.app_flex_tx.application_information));
|
||||||
*(*ui*external_app*flex_tx*);
|
*/external/flex_tx/*(*ui*external_app*flex_tx*);
|
||||||
} > ram_external_app_flex_tx
|
} > ram_external_app_flex_tx
|
||||||
|
|
||||||
.external_app_hard_reset : ALIGN(4) SUBALIGN(4)
|
.external_app_hard_reset : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_hard_reset.application_information));
|
KEEP(*(.external_app.app_hard_reset.application_information));
|
||||||
*(*ui*external_app*hard_reset*);
|
*/external/hard_reset/*(*ui*external_app*hard_reset*);
|
||||||
} > ram_external_app_hard_reset
|
} > ram_external_app_hard_reset
|
||||||
|
|
||||||
|
.external_app_secplustx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_secplustx.application_information));
|
||||||
|
*/external/secplustx/*(*ui*external_app*secplustx*);
|
||||||
|
} > ram_external_app_secplustx
|
||||||
|
|
||||||
|
.external_app_signal_hunter : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_signal_hunter.application_information));
|
||||||
|
*/external/signal_hunter/*(*ui*external_app*signal_hunter*);
|
||||||
|
} > ram_external_app_signal_hunter
|
||||||
|
|
||||||
|
.external_app_tetra_rx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_tetra_rx.application_information));
|
||||||
|
*/external/tetra_rx/*(*ui*external_app*tetra_rx*);
|
||||||
|
} > ram_external_app_tetra_rx
|
||||||
|
|
||||||
|
.external_app_adsbrx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_adsbrx.application_information));
|
||||||
|
*(*ui*external_app*adsbrx*);
|
||||||
|
} > ram_external_app_adsbrx
|
||||||
|
|
||||||
|
.external_app_ais_rx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_ais_rx.application_information));
|
||||||
|
*(*ui*external_app*ais_rx*);
|
||||||
|
} > ram_external_app_ais_rx
|
||||||
|
|
||||||
|
.external_app_aprs_rx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_aprs_rx.application_information));
|
||||||
|
*(*ui*external_app*aprs_rx*);
|
||||||
|
} > ram_external_app_aprs_rx
|
||||||
|
|
||||||
|
.external_app_aprs_tx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_aprs_tx.application_information));
|
||||||
|
*(*ui*external_app*aprs_tx*);
|
||||||
|
} > ram_external_app_aprs_tx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Mark Thompson
|
* Copyright (C) 2023 Mark Thompson
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Mark Thompson
|
* Copyright (C) 2023 Mark Thompson
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,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) 2020 euquiq
|
* Copyright (C) 2020 euquiq
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,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) 2020 euquiq
|
* Copyright (C) 2020 euquiq
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+38
-2
@@ -34,7 +34,7 @@ void KeeloqTXView::update_hop() {
|
|||||||
|
|
||||||
hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter;
|
hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter;
|
||||||
} else if (
|
} else if (
|
||||||
data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449") {
|
data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449" || data.mf_name == "Superrollo") {
|
||||||
hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter;
|
hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter;
|
||||||
} else if (
|
} else if (
|
||||||
data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") {
|
data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") {
|
||||||
@@ -75,6 +75,12 @@ void KeeloqTXView::update_payload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.mf_name == "Superrollo") {
|
||||||
|
text_payload.set(to_string_hex((uint64_t)(uint32_t)encrypt));
|
||||||
|
encode_data_gw60((uint32_t)encrypt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
payload = (uint64_t)fix << 32 | encrypt;
|
payload = (uint64_t)fix << 32 | encrypt;
|
||||||
|
|
||||||
uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64);
|
uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64);
|
||||||
@@ -83,6 +89,34 @@ void KeeloqTXView::update_payload() {
|
|||||||
encode_data();
|
encode_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Superrollo GW60 (HCS361) 67-bit OOK frame at Te=450us.
|
||||||
|
void KeeloqTXView::encode_data_gw60(uint32_t hop_enc) {
|
||||||
|
int bits[67];
|
||||||
|
for (uint32_t i = 0; i < 32; i++) bits[i] = (hop_enc >> i) & 1;
|
||||||
|
for (uint32_t i = 0; i < 28; i++) bits[32 + i] = (data.serial >> i) & 1;
|
||||||
|
for (uint32_t i = 0; i < 4; i++) bits[60 + i] = (data.btn >> i) & 1;
|
||||||
|
bits[64] = 1;
|
||||||
|
|
||||||
|
int crc0 = 0, crc1 = 0;
|
||||||
|
for (uint32_t i = 0; i < 65; i++) {
|
||||||
|
int new_crc1 = crc0 ^ bits[i];
|
||||||
|
int new_crc0 = new_crc1 ^ crc1;
|
||||||
|
crc0 = new_crc0 & 1;
|
||||||
|
crc1 = new_crc1 & 1;
|
||||||
|
}
|
||||||
|
bits[65] = crc0;
|
||||||
|
bits[66] = crc1;
|
||||||
|
|
||||||
|
std::string s{};
|
||||||
|
for (int i = 0; i < 9; i++) s += "100";
|
||||||
|
s += "11111111110000000000";
|
||||||
|
for (int i = 0; i < 67; i++) s += keeloq_fragments[bits[i]];
|
||||||
|
for (int i = 0; i < 18; i++) s += "0";
|
||||||
|
|
||||||
|
encoded_data = s;
|
||||||
|
pause_duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void KeeloqTXView::encode_data() {
|
void KeeloqTXView::encode_data() {
|
||||||
std::string fragments{};
|
std::string fragments{};
|
||||||
|
|
||||||
@@ -206,9 +240,11 @@ void KeeloqTXView::start_tx() {
|
|||||||
|
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
const double te_us = (data.mf_name == "Superrollo") ? 450.0 : 400.0;
|
||||||
|
|
||||||
baseband::set_ook_data(
|
baseband::set_ook_data(
|
||||||
bitstream_length,
|
bitstream_length,
|
||||||
OOK_SAMPLERATE * (400.0 / 1000000.0),
|
OOK_SAMPLERATE * (te_us / 1000000.0),
|
||||||
repeat,
|
repeat,
|
||||||
pause_duration);
|
pause_duration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ class KeeloqTXView : public View {
|
|||||||
std::string encoded_data{};
|
std::string encoded_data{};
|
||||||
|
|
||||||
void encode_data();
|
void encode_data();
|
||||||
|
void encode_data_gw60(uint32_t hop_enc);
|
||||||
|
|
||||||
uint32_t repeat = 4;
|
uint32_t repeat = 4;
|
||||||
uint32_t pause_duration = 0;
|
uint32_t pause_duration = 0;
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ using namespace portapack;
|
|||||||
namespace ui::external_app::mcu_temperature {
|
namespace ui::external_app::mcu_temperature {
|
||||||
|
|
||||||
void McuTemperatureWidget::paint(Painter& painter) {
|
void McuTemperatureWidget::paint(Painter& painter) {
|
||||||
const auto logger = portapack::temperature_logger;
|
|
||||||
|
|
||||||
const auto rect = screen_rect();
|
const auto rect = screen_rect();
|
||||||
const Color color_background{0, 0, 64};
|
const Color color_background{0, 0, 64};
|
||||||
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
|
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
|
||||||
const Color color_reticle{128, 128, 128};
|
const Color color_reticle{128, 128, 128};
|
||||||
|
|
||||||
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
const auto graph_width = static_cast<int>(temperature_logger.capacity()) * bar_width;
|
||||||
const Rect graph_rect{
|
const Rect graph_rect{
|
||||||
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
|
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
|
||||||
graph_width, rect.height()};
|
graph_width, rect.height()};
|
||||||
@@ -25,7 +23,7 @@ void McuTemperatureWidget::paint(Painter& painter) {
|
|||||||
painter.draw_rectangle(frame_rect, color_reticle);
|
painter.draw_rectangle(frame_rect, color_reticle);
|
||||||
painter.fill_rectangle(graph_rect, color_background);
|
painter.fill_rectangle(graph_rect, color_background);
|
||||||
|
|
||||||
const auto history = logger.history();
|
const auto history = temperature_logger.history();
|
||||||
for (size_t i = 0; i < history.size(); i++) {
|
for (size_t i = 0; i < history.size(); i++) {
|
||||||
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
||||||
const auto sample = history[i];
|
const auto sample = history[i];
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "memory_map.hpp"
|
#include "memory_map.hpp"
|
||||||
#include "irq_controls.hpp"
|
#include "irq_controls.hpp"
|
||||||
|
#include "temperature_logger.hpp"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -43,14 +43,25 @@ class McuTemperatureWidget : public Widget {
|
|||||||
explicit McuTemperatureWidget(
|
explicit McuTemperatureWidget(
|
||||||
Rect parent_rect)
|
Rect parent_rect)
|
||||||
: Widget{parent_rect} {
|
: Widget{parent_rect} {
|
||||||
|
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||||
|
this->on_tick_second();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
~McuTemperatureWidget() {
|
||||||
|
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||||
|
}
|
||||||
|
void on_tick_second() {
|
||||||
|
temperature_logger.second_tick();
|
||||||
|
set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
TemperatureLogger temperature_logger{};
|
||||||
|
|
||||||
using sample_t = uint32_t;
|
using sample_t = uint32_t;
|
||||||
using temperature_t = int32_t;
|
using temperature_t = int32_t;
|
||||||
|
SignalToken signal_token_tick_second{};
|
||||||
temperature_t temperature(const sample_t sensor_value) const;
|
temperature_t temperature(const sample_t sensor_value) const;
|
||||||
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
|
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
|
||||||
|
|
||||||
|
|||||||
+4
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace ui::external_app::mcu_temperature {
|
||||||
|
|
||||||
void TemperatureLogger::second_tick() {
|
void TemperatureLogger::second_tick() {
|
||||||
sample_phase++;
|
sample_phase++;
|
||||||
if (sample_phase >= sample_interval) {
|
if (sample_phase >= sample_interval) {
|
||||||
@@ -67,3 +69,5 @@ void TemperatureLogger::push_sample(const TemperatureLogger::sample_t sample) {
|
|||||||
samples_count++;
|
samples_count++;
|
||||||
sample_phase = 0;
|
sample_phase = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::mcu_temperature
|
||||||
+4
@@ -27,6 +27,8 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ui::external_app::mcu_temperature {
|
||||||
|
|
||||||
class TemperatureLogger {
|
class TemperatureLogger {
|
||||||
public:
|
public:
|
||||||
using sample_t = int8_t;
|
using sample_t = int8_t;
|
||||||
@@ -49,4 +51,6 @@ class TemperatureLogger {
|
|||||||
void push_sample(const sample_t sample);
|
void push_sample(const sample_t sample);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::mcu_temperature
|
||||||
|
|
||||||
#endif /*__TEMPERATURE_LOGGER_H__*/
|
#endif /*__TEMPERATURE_LOGGER_H__*/
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
|
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 HTotoo
|
* Copyright (C) 2024 HTotoo
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 HTotoo
|
* Copyright (C) 2024 HTotoo
|
||||||
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2024 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ SdOverUsbView::SdOverUsbView(NavigationView& nav)
|
|||||||
sdcStop(&SDCD1);
|
sdcStop(&SDCD1);
|
||||||
|
|
||||||
portapack::shutdown(true, false);
|
portapack::shutdown(true, false);
|
||||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base(), false);
|
||||||
m0_halt();
|
m0_halt();
|
||||||
/* will not return*/
|
/* will not return*/
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Synray
|
||||||
|
*
|
||||||
|
* 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_navigation.hpp"
|
||||||
|
#include "ui_secplustx.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_secplustx {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<SecplusTXView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::ui_secplustx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_secplustx.application_information"), used)) application_information_t _application_information_secplustx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::ui_secplustx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
/*.app_name = */ "Security+",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0b00100000,
|
||||||
|
0b00000000,
|
||||||
|
0b00100000,
|
||||||
|
0b00100000,
|
||||||
|
0b00100000,
|
||||||
|
0b01110000,
|
||||||
|
0b00100000,
|
||||||
|
0b00100000,
|
||||||
|
0b11100000,
|
||||||
|
0b00000111,
|
||||||
|
0b11110000,
|
||||||
|
0b00001111,
|
||||||
|
0b00110000,
|
||||||
|
0b00001100,
|
||||||
|
0b00110000,
|
||||||
|
0b00001100,
|
||||||
|
0b11110000,
|
||||||
|
0b00001111,
|
||||||
|
0b11110000,
|
||||||
|
0b00001111,
|
||||||
|
0b01110000,
|
||||||
|
0b00001101,
|
||||||
|
0b10110000,
|
||||||
|
0b00001110,
|
||||||
|
0b01110000,
|
||||||
|
0b00001101,
|
||||||
|
0b10110000,
|
||||||
|
0b00001110,
|
||||||
|
0b11110000,
|
||||||
|
0b00001111,
|
||||||
|
0b11100000,
|
||||||
|
0b00000111,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
|
/*.menu_location = */ app_location_t::TX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Clayton Smith (argilo@gmail.com)
|
||||||
|
*
|
||||||
|
* This file is part of secplus.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "secplustx.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_secplustx {
|
||||||
|
|
||||||
|
static void v2_calc_parity(const uint64_t fixed, uint32_t* data) {
|
||||||
|
uint32_t parity = (fixed >> 32) & 0xf;
|
||||||
|
int8_t offset;
|
||||||
|
|
||||||
|
*data &= 0xffff0fff;
|
||||||
|
for (offset = 0; offset < 32; offset += 4) {
|
||||||
|
parity ^= ((*data >> offset) & 0xf);
|
||||||
|
}
|
||||||
|
*data |= (parity << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode_v2_rolling(const uint32_t rolling,
|
||||||
|
uint32_t* rolling_halves) {
|
||||||
|
uint32_t rolling_reversed = 0;
|
||||||
|
int8_t i, half;
|
||||||
|
|
||||||
|
for (i = 0; i < 28; i++) {
|
||||||
|
rolling_reversed |= ((rolling >> i) & 1) << (28 - i - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rolling_halves[0] = 0;
|
||||||
|
rolling_halves[1] = 0;
|
||||||
|
|
||||||
|
for (half = 0; half < 2; half++) {
|
||||||
|
for (i = 0; i < 8; i += 2) {
|
||||||
|
rolling_halves[half] |= rolling_reversed % 3 << i;
|
||||||
|
rolling_reversed /= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (half = 0; half < 2; half++) {
|
||||||
|
for (i = 10; i < 18; i += 2) {
|
||||||
|
rolling_halves[half] |= rolling_reversed % 3 << i;
|
||||||
|
rolling_reversed /= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rolling_halves[0] |= (rolling_reversed % 3) << 8;
|
||||||
|
rolling_reversed /= 3;
|
||||||
|
|
||||||
|
rolling_halves[1] |= (rolling_reversed % 3) << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int8_t ORDER[16] = {9, 33, 6, -1, 24, 18, 36, -1,
|
||||||
|
24, 36, 6, -1, -1, -1, -1, -1};
|
||||||
|
static const int8_t INVERT[16] = {6, 2, 1, -1, 7, 5, 3, -1,
|
||||||
|
4, 0, 5, -1, -1, -1, -1, -1};
|
||||||
|
|
||||||
|
static void v2_scramble(const uint32_t* parts, const uint8_t frame_type, uint8_t* packet_half) {
|
||||||
|
const int8_t order = ORDER[packet_half[0] >> 4];
|
||||||
|
const int8_t invert = INVERT[packet_half[0] & 0xf];
|
||||||
|
int8_t i;
|
||||||
|
uint8_t out_offset = 10;
|
||||||
|
int8_t end;
|
||||||
|
uint32_t parts_permuted[3];
|
||||||
|
|
||||||
|
end = (frame_type == 0 ? 5 : 8);
|
||||||
|
for (i = 1; i < end; i++) {
|
||||||
|
packet_half[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts_permuted[0] =
|
||||||
|
(invert & 4) ? ~parts[(order >> 4) & 3] : parts[(order >> 4) & 3];
|
||||||
|
parts_permuted[1] =
|
||||||
|
(invert & 2) ? ~parts[(order >> 2) & 3] : parts[(order >> 2) & 3];
|
||||||
|
parts_permuted[2] = (invert & 1) ? ~parts[order & 3] : parts[order & 3];
|
||||||
|
|
||||||
|
end = (frame_type == 0 ? 8 : 0);
|
||||||
|
for (i = 18 - 1; i >= end; i--) {
|
||||||
|
packet_half[out_offset >> 3] |= ((parts_permuted[0] >> i) & 1)
|
||||||
|
<< (7 - (out_offset % 8));
|
||||||
|
out_offset++;
|
||||||
|
packet_half[out_offset >> 3] |= ((parts_permuted[1] >> i) & 1)
|
||||||
|
<< (7 - (out_offset % 8));
|
||||||
|
out_offset++;
|
||||||
|
packet_half[out_offset >> 3] |= ((parts_permuted[2] >> i) & 1)
|
||||||
|
<< (7 - (out_offset % 8));
|
||||||
|
out_offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode_v2_half_parts(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
|
||||||
|
uint32_t parts[3];
|
||||||
|
|
||||||
|
parts[0] = ((fixed >> 10) << 8) | (data >> 8);
|
||||||
|
parts[1] = ((fixed & 0x3ff) << 8) | (data & 0xff);
|
||||||
|
parts[2] = rolling;
|
||||||
|
|
||||||
|
packet_half[0] = (uint8_t)rolling;
|
||||||
|
|
||||||
|
v2_scramble(parts, frame_type, packet_half);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int8_t v2_check_limits(const uint32_t rolling, const uint64_t fixed) {
|
||||||
|
if ((rolling >> 28) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fixed >> 40) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode_v2_half(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
|
||||||
|
encode_v2_half_parts(rolling, fixed, data, frame_type, packet_half);
|
||||||
|
|
||||||
|
/* shift indicator two bits to the right */
|
||||||
|
packet_half[1] |= (packet_half[0] & 0x3) << 6;
|
||||||
|
packet_half[0] >>= 2;
|
||||||
|
|
||||||
|
/* set frame type */
|
||||||
|
packet_half[0] |= (frame_type << 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2) {
|
||||||
|
int8_t err = 0;
|
||||||
|
uint32_t rolling_halves[2];
|
||||||
|
|
||||||
|
err = v2_check_limits(rolling, fixed);
|
||||||
|
if (err < 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode_v2_rolling(rolling, rolling_halves);
|
||||||
|
v2_calc_parity(fixed, &data);
|
||||||
|
|
||||||
|
encode_v2_half(rolling_halves[0], fixed >> 20, data >> 16, frame_type,
|
||||||
|
packet1);
|
||||||
|
encode_v2_half(rolling_halves[1], fixed & 0xfffff, data & 0xffff, frame_type,
|
||||||
|
packet2);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace ui::external_app::ui_secplustx
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Clayton Smith (argilo@gmail.com)
|
||||||
|
*
|
||||||
|
* This file is part of secplus.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SECPLUSTX__
|
||||||
|
#define __SECPLUSTX__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_secplustx {
|
||||||
|
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
#include "ui_secplustx.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "secplustx.hpp"
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "file_reader.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "optional.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
using namespace ui;
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_secplustx {
|
||||||
|
|
||||||
|
static constexpr uint8_t secplus_repeat_count = 3;
|
||||||
|
static constexpr float secplus_sample_rate = OOK_SAMPLERATE / 4000.0f;
|
||||||
|
|
||||||
|
Optional<SecplusData> SecplusTXView::read_secplus_file(const fs::path& file_path) {
|
||||||
|
File file{};
|
||||||
|
if (auto error = file.open(file_path)) return {};
|
||||||
|
|
||||||
|
std::string raw = *FileLineReader{file}.begin();
|
||||||
|
std::vector chunks = split_string(raw, ';');
|
||||||
|
if (chunks.empty()) return {};
|
||||||
|
|
||||||
|
SecplusData data{};
|
||||||
|
data.version = static_cast<SecplusVersion>(std::strtoul(chunks[0].data(), NULL, 10));
|
||||||
|
switch (data.version) {
|
||||||
|
case SecplusVersion::V1:
|
||||||
|
return {}; // unimplemented
|
||||||
|
case SecplusVersion::V2: {
|
||||||
|
if (chunks.size() != 6) return {};
|
||||||
|
data.name = std::string{chunks[1]};
|
||||||
|
if (data.name.empty()) data.name = "Remote";
|
||||||
|
data.has_data = std::strtoul(chunks[2].data(), NULL, 10);
|
||||||
|
data.fixed_code = std::strtoull(chunks[3].data(), NULL, 16);
|
||||||
|
data.rolling_code = std::strtoul(chunks[4].data(), NULL, 16);
|
||||||
|
data.data = std::strtoul(chunks[5].data(), NULL, 16);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecplusTXView::write_secplus_file(const fs::path& file_path, const SecplusData& data) {
|
||||||
|
ensure_directory(secplus_dir);
|
||||||
|
File file{};
|
||||||
|
if (auto error = file.create(file_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.version) {
|
||||||
|
case SecplusVersion::V1:
|
||||||
|
return false; // unimplemented
|
||||||
|
case SecplusVersion::V2: {
|
||||||
|
std::string formatted = to_string_dec_uint(static_cast<uint8_t>(data.version));
|
||||||
|
formatted += ';';
|
||||||
|
formatted += data.name;
|
||||||
|
formatted += ';';
|
||||||
|
formatted += to_string_dec_uint(data.has_data);
|
||||||
|
formatted += ';';
|
||||||
|
formatted += to_string_hex(data.fixed_code);
|
||||||
|
formatted += ';';
|
||||||
|
formatted += to_string_hex(data.rolling_code);
|
||||||
|
formatted += ';';
|
||||||
|
formatted += to_string_hex(data.data);
|
||||||
|
file.write_line(formatted);
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecplusTXView::SecplusTXView(NavigationView& nav)
|
||||||
|
: nav_{nav} {
|
||||||
|
if (!fs::file_exists(file_path)) write_secplus_file(file_path, data);
|
||||||
|
baseband::run_image(portapack::spi_flash::image_tag_ook);
|
||||||
|
|
||||||
|
add_children({&button_open,
|
||||||
|
&button_save,
|
||||||
|
&field_name,
|
||||||
|
&labels,
|
||||||
|
&field_fixed,
|
||||||
|
&field_rolling,
|
||||||
|
&has_data,
|
||||||
|
&field_data,
|
||||||
|
&learn_mode,
|
||||||
|
&autosave,
|
||||||
|
&progressbar,
|
||||||
|
&tx_view});
|
||||||
|
|
||||||
|
button_open.on_select = [this](const Button&) {
|
||||||
|
ensure_directory(secplus_dir);
|
||||||
|
auto open_view = nav_.push<FileLoadView>(".SECPLUS");
|
||||||
|
open_view->push_dir(secplus_dir);
|
||||||
|
open_view->on_changed = [this](fs::path path) { file_path = path.string(); };
|
||||||
|
nav_.set_on_pop([this]() { reload_data(); });
|
||||||
|
};
|
||||||
|
button_save.on_select = [this](const Button&) { write_secplus_file(file_path, data); };
|
||||||
|
|
||||||
|
field_name.on_select = [this, &nav](TextField&) {
|
||||||
|
buffer = data.name;
|
||||||
|
text_prompt(nav_, buffer, field_name.parent_rect().width() / UI_POS_DEFAULT_WIDTH, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
|
||||||
|
data.name = new_name;
|
||||||
|
field_name.set_text(new_name);
|
||||||
|
save_data();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
field_fixed.on_change = [this](SymField& field) { data.fixed_code = field.to_integer(); };
|
||||||
|
field_rolling.on_change = [this](SymField& field) { data.rolling_code = field.to_integer(); };
|
||||||
|
field_data.on_change = [this](SymField& field) { data.data = field.to_integer(); };
|
||||||
|
has_data.on_select = [this](Checkbox& checkbox, bool value) {
|
||||||
|
data.has_data = value;
|
||||||
|
// fade if inactive, otherwise use default style
|
||||||
|
const Style* new_style = value ? &style() : Theme::getInstance()->fg_medium;
|
||||||
|
checkbox.set_style(new_style);
|
||||||
|
field_data.set_style(new_style);
|
||||||
|
field_data.set_focusable(value);
|
||||||
|
};
|
||||||
|
tx_view.on_edit_frequency = [this]() {
|
||||||
|
auto new_view = nav_.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||||
|
new_view->on_changed = [](rf::Frequency f) {
|
||||||
|
transmitter_model.set_target_frequency(f);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_view.on_start = [this]() { start_tx(); };
|
||||||
|
tx_view.on_stop = [this]() {
|
||||||
|
baseband::kill_ook();
|
||||||
|
stop_tx();
|
||||||
|
};
|
||||||
|
|
||||||
|
autosave.set_value(true);
|
||||||
|
reload_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecplusTXView::save_data() {
|
||||||
|
if (autosave.value()) write_secplus_file(file_path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecplusTXView::reload_data() {
|
||||||
|
if (auto result = read_secplus_file(file_path)) data = std::move(*result);
|
||||||
|
// always update data, use default values if read failed
|
||||||
|
field_name.set_text(data.name);
|
||||||
|
has_data.set_value(data.has_data);
|
||||||
|
field_rolling.set_value(data.rolling_code);
|
||||||
|
field_fixed.set_value(data.fixed_code);
|
||||||
|
field_data.set_value(data.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecplusTXView::start_tx() {
|
||||||
|
uint8_t packet1[8]{};
|
||||||
|
uint8_t packet2[8]{};
|
||||||
|
size_t bitstream_length = 0;
|
||||||
|
|
||||||
|
constexpr uint8_t packet1_indicator = 0b00;
|
||||||
|
constexpr uint8_t packet2_indicator = 0b01;
|
||||||
|
auto encode_packet = [&bitstream_length](uint8_t indicator, auto& packet, bool has_data) {
|
||||||
|
constexpr uint32_t preamble = 0b0000000000000000'1111;
|
||||||
|
constexpr uint32_t blank_size = 24;
|
||||||
|
auto manchester_encode = [&bitstream_length](auto& x, uint32_t size) {
|
||||||
|
for (uint32_t i = 0; i < size; ++i) {
|
||||||
|
bool bit = (x >> (size - 1 - i)) & 1;
|
||||||
|
encoders::bitstream_append(bitstream_length, 2, bit ? 0b01 : 0b10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
manchester_encode(preamble, 20);
|
||||||
|
manchester_encode(indicator, 2);
|
||||||
|
for (uint8_t byte = 0; byte < (has_data ? 8 : 5); ++byte) manchester_encode(packet[byte], 8);
|
||||||
|
encoders::bitstream_append(bitstream_length, blank_size, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (encode_v2(data.rolling_code, data.fixed_code, data.data, data.has_data, packet1, packet2) < 0) {
|
||||||
|
nav_.display_modal("Error", "Invalid rolling/fixed code");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
encode_packet(packet1_indicator, packet1, has_data.value());
|
||||||
|
encode_packet(packet2_indicator, packet2, has_data.value());
|
||||||
|
|
||||||
|
if (!learn_mode.value()) {
|
||||||
|
field_rolling.set_value(++data.rolling_code);
|
||||||
|
field_rolling.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar.set_max(secplus_repeat_count);
|
||||||
|
progressbar.set_value(1);
|
||||||
|
|
||||||
|
tx_view.set_transmitting(true);
|
||||||
|
transmitter_model.enable();
|
||||||
|
baseband::set_ook_data(
|
||||||
|
bitstream_length,
|
||||||
|
secplus_sample_rate,
|
||||||
|
secplus_repeat_count,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecplusTXView::stop_tx() {
|
||||||
|
transmitter_model.disable();
|
||||||
|
progressbar.set_value(0);
|
||||||
|
tx_view.set_transmitting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecplusTXView::on_tx_progress(uint32_t progress, bool done) {
|
||||||
|
progressbar.set_value(progress + 1);
|
||||||
|
if (done) {
|
||||||
|
stop_tx();
|
||||||
|
save_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecplusTXView::~SecplusTXView() {
|
||||||
|
transmitter_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
save_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::ui_secplustx
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#ifndef __UI_SECPLUSTX__
|
||||||
|
#define __UI_SECPLUSTX__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
|
#include "ui_transmitter.hpp"
|
||||||
|
#include "transmitter_model.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
#include "app_settings.hpp"
|
||||||
|
#include "radio_state.hpp"
|
||||||
|
#include "encoders.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_secplustx {
|
||||||
|
|
||||||
|
enum class SecplusVersion : uint8_t {
|
||||||
|
V1,
|
||||||
|
V2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SecplusData {
|
||||||
|
SecplusVersion version;
|
||||||
|
std::string name;
|
||||||
|
bool has_data;
|
||||||
|
uint64_t fixed_code;
|
||||||
|
uint32_t rolling_code;
|
||||||
|
uint32_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SecplusTXView : public View {
|
||||||
|
public:
|
||||||
|
void focus() override { button_open.focus(); }
|
||||||
|
SecplusTXView(NavigationView& nav);
|
||||||
|
~SecplusTXView();
|
||||||
|
|
||||||
|
std::string title() const override {
|
||||||
|
return "Security+";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string file_path = (secplus_dir / "DEFAULT.SECPLUS").string();
|
||||||
|
SecplusData data{SecplusVersion::V2, "Remote", false, 0, 0, 0};
|
||||||
|
std::string buffer{};
|
||||||
|
|
||||||
|
NavigationView& nav_;
|
||||||
|
TxRadioState radio_state_{
|
||||||
|
315000000,
|
||||||
|
1750000,
|
||||||
|
OOK_SAMPLERATE};
|
||||||
|
|
||||||
|
app_settings::SettingsManager settings_{"tx_secplus", app_settings::Mode::TX, {{"file_path"sv, &file_path}}};
|
||||||
|
|
||||||
|
Button button_open{{UI_POS_X(1), UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Open"};
|
||||||
|
Button button_save{{screen_width / 2, UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Save"};
|
||||||
|
|
||||||
|
// remote data
|
||||||
|
TextField field_name{{UI_POS_X(1), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(2), UI_POS_HEIGHT(1)}, "Remote"};
|
||||||
|
Labels labels{
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(4)}, "Fixed:", Theme::getInstance()->fg_medium->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(5)}, "Rolling:", Theme::getInstance()->fg_medium->foreground}};
|
||||||
|
SymField field_fixed{{UI_POS_X(10), UI_POS_Y(4)}, 10, SymField::Type::Hex, true};
|
||||||
|
SymField field_rolling{{UI_POS_X(10), UI_POS_Y(5)}, 7, SymField::Type::Hex, true};
|
||||||
|
Checkbox has_data{{UI_POS_X(1), UI_POS_Y(6)}, 5, "Data:", true};
|
||||||
|
SymField field_data{{UI_POS_X(10), UI_POS_Y(6)}, 8, SymField::Type::Hex, true};
|
||||||
|
|
||||||
|
// options
|
||||||
|
Checkbox learn_mode{{UI_POS_X(1), UI_POS_Y(7)}, 5, "Learn", true};
|
||||||
|
Checkbox autosave{{UI_POS_X(1), UI_POS_Y(8)}, 8, "Autosave", true};
|
||||||
|
|
||||||
|
ProgressBar progressbar{{UI_POS_X(2), UI_POS_Y_BOTTOM(5.75), UI_POS_WIDTH_REMAINING(4), UI_POS_HEIGHT(1)}};
|
||||||
|
|
||||||
|
TransmitterView tx_view{UI_POS_Y_BOTTOM(4), 1000000, 0};
|
||||||
|
|
||||||
|
Optional<SecplusData> read_secplus_file(const std::filesystem::path& file_path);
|
||||||
|
bool write_secplus_file(const std::filesystem::path& file_path, const SecplusData& data);
|
||||||
|
void save_data();
|
||||||
|
void reload_data();
|
||||||
|
void start_tx();
|
||||||
|
void stop_tx();
|
||||||
|
|
||||||
|
void on_tx_progress(uint32_t progress, bool done);
|
||||||
|
|
||||||
|
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::external_app::ui_secplustx
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Matej Sochan
|
||||||
|
*
|
||||||
|
* 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_signal_hunter.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::signal_hunter {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<SignalHunterAppView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::signal_hunter
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
__attribute__((section(".external_app.app_signal_hunter.application_information"), used)) application_information_t _application_information_signal_hunter = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::signal_hunter::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
/*.app_name = */ "SigHunter",
|
||||||
|
/*.bitmap_data = */ {0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0x98, 0x19, 0x8C, 0x31, 0x84, 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x21, 0x8C, 0x31, 0x98, 0x19, 0xF0, 0x0F, 0xE0, 0x07, 0x80, 0x01, 0x80, 0x01},
|
||||||
|
/*.icon_color = */ ui::Color::green().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = */ {'H', 'U', 'N', 'T'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Matej Sochan
|
||||||
|
*
|
||||||
|
* 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_signal_hunter.hpp"
|
||||||
|
#include "receiver_model.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "freqman_db.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
#include "rtc_time.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace ui::external_app::signal_hunter {
|
||||||
|
|
||||||
|
// --- TAB 1: Main View ---
|
||||||
|
HunterMainView::HunterMainView(Rect parent_rect, SignalHunterAppView& parent)
|
||||||
|
: View(parent_rect), parent_app(parent) {
|
||||||
|
add_children({&text_current_freq, &text_status, &button_start_stop, &text_hits});
|
||||||
|
update_frequency(433920000);
|
||||||
|
|
||||||
|
button_start_stop.on_select = [this](Button&) {
|
||||||
|
parent_app.is_hunting = !parent_app.is_hunting;
|
||||||
|
if (parent_app.is_hunting) {
|
||||||
|
button_start_stop.set_text("STOP");
|
||||||
|
// Set initial frequency based on set mode (single freq / freq hopping)
|
||||||
|
if (parent_app.freq_hop_mode && !parent_app.frequency_list.empty()) {
|
||||||
|
parent_app.current_freq_index = 0;
|
||||||
|
auto freq = parent_app.frequency_list[0];
|
||||||
|
receiver_model.set_target_frequency(freq);
|
||||||
|
update_frequency(freq);
|
||||||
|
} else {
|
||||||
|
receiver_model.set_target_frequency(parent_app.single_frequency);
|
||||||
|
update_frequency(parent_app.single_frequency);
|
||||||
|
}
|
||||||
|
update_status("HUNTING...", Theme::getInstance()->fg_green);
|
||||||
|
parent_app.send_hunter_config(true);
|
||||||
|
} else {
|
||||||
|
button_start_stop.set_text("START");
|
||||||
|
update_status("IDLE", Theme::getInstance()->fg_light);
|
||||||
|
set_recording_state(false);
|
||||||
|
parent_app.send_hunter_config(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::on_show() {
|
||||||
|
if (!parent_app.freq_hop_mode) {
|
||||||
|
current_freq_ = receiver_model.target_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_frequency(current_freq_); // force repaint
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::focus() {
|
||||||
|
if (!parent_app.freq_hop_mode) {
|
||||||
|
current_freq_ = receiver_model.target_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_frequency(current_freq_);
|
||||||
|
button_start_stop.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::set_recording_state(bool recording) {
|
||||||
|
if (recording)
|
||||||
|
text_current_freq.set_style(Theme::getInstance()->fg_red);
|
||||||
|
else
|
||||||
|
text_current_freq.set_style(Theme::getInstance()->fg_light);
|
||||||
|
|
||||||
|
text_current_freq.set_dirty(); // force ui update
|
||||||
|
update_frequency(current_freq_); // repaint with the new color
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::update_frequency(rf::Frequency freq) {
|
||||||
|
current_freq_ = freq;
|
||||||
|
text_current_freq.set(to_string_dec_uint(freq) + " Hz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::update_status(const std::string& status, const Style* style) {
|
||||||
|
text_status.set(status);
|
||||||
|
text_status.set_style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterMainView::update_hits(uint32_t hits) {
|
||||||
|
text_hits.set("Hits: " + to_string_dec_uint(hits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TAB 2: Freqs View ---
|
||||||
|
HunterFreqsView::HunterFreqsView(Rect parent_rect, SignalHunterAppView& parent)
|
||||||
|
: View(parent_rect), parent_app(parent) {
|
||||||
|
add_children({&button_load_file,
|
||||||
|
&button_clear,
|
||||||
|
&labels,
|
||||||
|
&field_dwell,
|
||||||
|
&text_loaded_info});
|
||||||
|
|
||||||
|
field_dwell.set_value(parent_app.hop_dwell_ms);
|
||||||
|
field_dwell.on_change = [this](int32_t v) { parent_app.hop_dwell_ms = v; };
|
||||||
|
|
||||||
|
button_load_file.on_select = [this](Button&) {
|
||||||
|
auto* view = parent_app.get_nav().push<FileLoadView>(".TXT");
|
||||||
|
if (view) {
|
||||||
|
view->on_changed = [this](std::filesystem::path path) {
|
||||||
|
parent_app.frequency_list.clear();
|
||||||
|
parent_app.current_freq_index = 0;
|
||||||
|
FreqmanDB db{};
|
||||||
|
db.open(path);
|
||||||
|
for (const auto& entry : db) {
|
||||||
|
if (entry.frequency_a > 0)
|
||||||
|
parent_app.frequency_list.push_back(entry.frequency_a);
|
||||||
|
}
|
||||||
|
parent_app.freqman_file = path.stem().string();
|
||||||
|
|
||||||
|
// Switch to HOP mode if a file was loaded
|
||||||
|
if (!parent_app.frequency_list.empty())
|
||||||
|
parent_app.freq_hop_mode = true;
|
||||||
|
|
||||||
|
update_list_count();
|
||||||
|
|
||||||
|
// Update the mode display in the Config tab
|
||||||
|
if (parent_app.get_config_view()) {
|
||||||
|
parent_app.get_config_view()->update_mode_display();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button_clear.on_select = [this](Button&) {
|
||||||
|
parent_app.frequency_list.clear();
|
||||||
|
parent_app.current_freq_index = 0;
|
||||||
|
parent_app.freq_hop_mode = false;
|
||||||
|
|
||||||
|
update_list_count();
|
||||||
|
|
||||||
|
// Update the mode display in the Config tab
|
||||||
|
if (parent_app.get_config_view()) {
|
||||||
|
parent_app.get_config_view()->update_mode_display();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterFreqsView::update_list_count() {
|
||||||
|
text_loaded_info.set("Loaded: " +
|
||||||
|
to_string_dec_uint(parent_app.frequency_list.size()) + " freqs");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterFreqsView::focus() {
|
||||||
|
button_load_file.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TAB 3: Config View ---
|
||||||
|
HunterConfigView::HunterConfigView(Rect parent_rect, SignalHunterAppView& parent)
|
||||||
|
: View(parent_rect), parent_app(parent), field_single_freq{{UI_POS_X(2), UI_POS_Y(4)}, parent.get_nav()} {
|
||||||
|
add_children({&button_mode,
|
||||||
|
&field_single_freq,
|
||||||
|
&labels,
|
||||||
|
&field_threshold,
|
||||||
|
&field_hang_time,
|
||||||
|
&text_info_config});
|
||||||
|
|
||||||
|
button_mode.on_select = [this](Button&) {
|
||||||
|
parent_app.freq_hop_mode = !parent_app.freq_hop_mode;
|
||||||
|
update_mode_display();
|
||||||
|
};
|
||||||
|
|
||||||
|
field_single_freq.set_value(parent_app.single_frequency);
|
||||||
|
|
||||||
|
field_threshold.set_value(parent_app.energy_threshold);
|
||||||
|
field_threshold.on_change = [this](int32_t v) { parent_app.energy_threshold = v; };
|
||||||
|
|
||||||
|
field_hang_time.set_value(parent_app.hangtime_ms);
|
||||||
|
field_hang_time.on_change = [this](int32_t v) { parent_app.hangtime_ms = v; };
|
||||||
|
|
||||||
|
update_mode_display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterConfigView::on_show() {
|
||||||
|
auto current = receiver_model.target_frequency();
|
||||||
|
parent_app.single_frequency = current;
|
||||||
|
field_single_freq.set_value(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterConfigView::update_mode_display() {
|
||||||
|
if (parent_app.freq_hop_mode)
|
||||||
|
button_mode.set_text("MODE: HOP (" +
|
||||||
|
to_string_dec_uint(parent_app.frequency_list.size()) + " freqs)");
|
||||||
|
else
|
||||||
|
button_mode.set_text("MODE: SINGLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HunterConfigView::focus() {
|
||||||
|
field_threshold.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main App View ---
|
||||||
|
SignalHunterAppView::SignalHunterAppView(ui::NavigationView& nav)
|
||||||
|
: frequency_list{},
|
||||||
|
nav_(nav) {
|
||||||
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||||
|
|
||||||
|
receiver_model.set_target_frequency(433920000);
|
||||||
|
receiver_model.set_baseband_bandwidth(1750000);
|
||||||
|
receiver_model.set_sampling_rate(2000000);
|
||||||
|
receiver_model.enable();
|
||||||
|
|
||||||
|
view_main = std::make_unique<HunterMainView>(content_rect, *this);
|
||||||
|
view_freqs = std::make_unique<HunterFreqsView>(content_rect, *this);
|
||||||
|
view_config = std::make_unique<HunterConfigView>(content_rect, *this);
|
||||||
|
|
||||||
|
tab_view = std::make_unique<TabView>(std::initializer_list<TabView::TabDef>{
|
||||||
|
{"Target", Color::cyan(), view_main.get()},
|
||||||
|
{"Freqs", Color::green(), view_freqs.get()},
|
||||||
|
{"Config", Color::yellow(), view_config.get()}});
|
||||||
|
tab_view->set_parent_rect(view_rect);
|
||||||
|
|
||||||
|
add_children({&field_lna,
|
||||||
|
&field_vga,
|
||||||
|
&field_rf_amp,
|
||||||
|
&rssi,
|
||||||
|
tab_view.get(),
|
||||||
|
view_main.get(),
|
||||||
|
view_freqs.get(),
|
||||||
|
view_config.get()});
|
||||||
|
|
||||||
|
view_freqs->hidden(true);
|
||||||
|
view_config->hidden(true);
|
||||||
|
|
||||||
|
using namespace app_settings;
|
||||||
|
settings_ = std::make_unique<SettingsManager>(
|
||||||
|
"ext_signal_hunter", Mode::RX,
|
||||||
|
SettingBindings{
|
||||||
|
{"threshold"sv, &energy_threshold},
|
||||||
|
{"hangtime_ms"sv, &hangtime_ms},
|
||||||
|
{"hop_dwell_ms"sv, &hop_dwell_ms},
|
||||||
|
{"single_freq"sv, &single_frequency},
|
||||||
|
{"file"sv, &freqman_file}});
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalHunterAppView::~SignalHunterAppView() {
|
||||||
|
if (!freq_hop_mode) {
|
||||||
|
single_frequency = receiver_model.target_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely shutdown recording
|
||||||
|
stop_recording();
|
||||||
|
receiver_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalHunterAppView::focus() {
|
||||||
|
if (tab_view)
|
||||||
|
tab_view->focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TIMER LOGIC: Based on display refresh rate
|
||||||
|
void SignalHunterAppView::on_frame_sync() {
|
||||||
|
// Timer running only if, HUNTING, in HOPPING mode, not recording + we have the freqs list
|
||||||
|
if (is_hunting && freq_hop_mode && !capture_thread && !frequency_list.empty()) {
|
||||||
|
hop_timer_ms += 17; // 1 frame in a 60 Hz display ~ 16.6 ms
|
||||||
|
|
||||||
|
// if Dwell Time has passed, change frequency
|
||||||
|
if (hop_timer_ms >= hop_dwell_ms) {
|
||||||
|
hop_timer_ms = 0;
|
||||||
|
|
||||||
|
current_freq_index = (current_freq_index + 1) % frequency_list.size();
|
||||||
|
auto next_freq = frequency_list[current_freq_index];
|
||||||
|
receiver_model.set_target_frequency(next_freq);
|
||||||
|
|
||||||
|
if (view_main) {
|
||||||
|
view_main->update_frequency(next_freq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hop_timer_ms = 0; // if not hunting / recording -> timer = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recording control logic via M0 <-> M4 IPC
|
||||||
|
void SignalHunterAppView::send_hunter_config(bool start) {
|
||||||
|
// Send config to baseband processor via IPC mechanism
|
||||||
|
baseband::set_hunter_config(energy_threshold, hangtime_ms, start);
|
||||||
|
|
||||||
|
if (!start) {
|
||||||
|
stop_recording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalHunterAppView::on_hunter_trigger(const HunterTriggerMessage* /*message*/) {
|
||||||
|
// M4 has detected signal above threshold - start I/Q capture
|
||||||
|
if (!capture_thread && is_hunting) {
|
||||||
|
trigger_hits++;
|
||||||
|
view_main->update_hits(trigger_hits);
|
||||||
|
view_main->update_status("RECORDING!", Theme::getInstance()->fg_red);
|
||||||
|
view_main->set_recording_state(true);
|
||||||
|
|
||||||
|
start_recording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalHunterAppView::on_hunter_stop(const HunterStopMessage*) {
|
||||||
|
if (capture_thread) {
|
||||||
|
stop_recording();
|
||||||
|
view_main->set_recording_state(false);
|
||||||
|
|
||||||
|
if (is_hunting) {
|
||||||
|
if (freq_hop_mode && !frequency_list.empty()) {
|
||||||
|
// Next frequency
|
||||||
|
current_freq_index = (current_freq_index + 1) % frequency_list.size();
|
||||||
|
auto next_freq = frequency_list[current_freq_index];
|
||||||
|
receiver_model.set_target_frequency(next_freq);
|
||||||
|
view_main->update_frequency(next_freq);
|
||||||
|
|
||||||
|
// Reset the timer
|
||||||
|
hop_timer_ms = 0;
|
||||||
|
}
|
||||||
|
view_main->update_status("HUNTING...", Theme::getInstance()->fg_green);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalHunterAppView::start_recording() {
|
||||||
|
rtc::RTC datetime{};
|
||||||
|
rtc_time::now(datetime);
|
||||||
|
|
||||||
|
// Generate timestamped filename for capture session
|
||||||
|
const std::string capture_filename = "HNT_" +
|
||||||
|
to_string_dec_uint(datetime.year(), 4, '0') +
|
||||||
|
to_string_dec_uint(datetime.month(), 2, '0') +
|
||||||
|
to_string_dec_uint(datetime.day(), 2, '0') + "T" +
|
||||||
|
to_string_dec_uint(datetime.hour(), 2, '0') +
|
||||||
|
to_string_dec_uint(datetime.minute(), 2, '0') +
|
||||||
|
to_string_dec_uint(datetime.second(), 2, '0');
|
||||||
|
|
||||||
|
// Use FileWriter for direct raw I/Q dump to avoid M0 CPU bottlenecks.
|
||||||
|
// FileConvertWriter is heavier
|
||||||
|
auto writer = std::make_unique<FileWriter>();
|
||||||
|
auto create_error = writer->create(std::filesystem::path(u"/CAPTURES") / (capture_filename + ".C16"));
|
||||||
|
|
||||||
|
if (create_error.is_valid()) {
|
||||||
|
if (view_main) view_main->update_status("SD ERROR", Theme::getInstance()->fg_red);
|
||||||
|
|
||||||
|
send_hunter_config(false);
|
||||||
|
is_hunting = false;
|
||||||
|
if (view_main) view_main->set_start_button_text("START");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_capture_filename = capture_filename;
|
||||||
|
|
||||||
|
capture_thread = std::make_unique<CaptureThread>(
|
||||||
|
std::move(writer),
|
||||||
|
4096,
|
||||||
|
4,
|
||||||
|
[]() {},
|
||||||
|
[](File::Error) {});
|
||||||
|
|
||||||
|
if (view_main) view_main->update_status("RECORDING!", Theme::getInstance()->fg_red);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalHunterAppView::stop_recording() {
|
||||||
|
// Stop I/Q capture and release SD card for metadata write
|
||||||
|
capture_thread.reset();
|
||||||
|
|
||||||
|
// Generate metadata file after capture completes
|
||||||
|
if (!current_capture_filename.empty()) {
|
||||||
|
File txt_file;
|
||||||
|
auto txt_error = txt_file.create(std::filesystem::path(u"/CAPTURES") / (current_capture_filename + ".TXT"));
|
||||||
|
|
||||||
|
// is_valid() returns true only if error is present (counter-intuitive)
|
||||||
|
if (!txt_error.is_valid()) {
|
||||||
|
std::string metadata =
|
||||||
|
"center_frequency=" + to_string_dec_uint(receiver_model.target_frequency()) + "\n" +
|
||||||
|
// Divide main sample rate by decimation factor (8) to match C16 sample rate
|
||||||
|
"sample_rate=" + to_string_dec_uint(receiver_model.sampling_rate() / 8) + "\n";
|
||||||
|
|
||||||
|
txt_file.write(metadata.c_str(), metadata.length());
|
||||||
|
}
|
||||||
|
// Clear filename for next capture cycle
|
||||||
|
current_capture_filename.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::signal_hunter
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Matej Sochan
|
||||||
|
*
|
||||||
|
* 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_SIGNAL_HUNTER_H__
|
||||||
|
#define __UI_SIGNAL_HUNTER_H__
|
||||||
|
|
||||||
|
#include "app_settings.hpp"
|
||||||
|
#include "rf_path.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_receiver.hpp"
|
||||||
|
#include "ui_tabview.hpp"
|
||||||
|
#include "ui_freq_field.hpp"
|
||||||
|
#include "capture_thread.hpp"
|
||||||
|
#include "message.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace ui::external_app::signal_hunter {
|
||||||
|
|
||||||
|
class SignalHunterAppView;
|
||||||
|
|
||||||
|
// --- TAB 1: Main View ---
|
||||||
|
class HunterMainView : public View {
|
||||||
|
public:
|
||||||
|
HunterMainView(Rect parent_rect, SignalHunterAppView& parent);
|
||||||
|
void on_show() override;
|
||||||
|
void focus() override;
|
||||||
|
void update_status(const std::string& status, const Style* style);
|
||||||
|
void update_hits(uint32_t hits);
|
||||||
|
void update_frequency(rf::Frequency freq);
|
||||||
|
void set_recording_state(bool recording);
|
||||||
|
void set_start_button_text(const std::string& text) { button_start_stop.set_text(text); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SignalHunterAppView& parent_app;
|
||||||
|
rf::Frequency current_freq_{433920000};
|
||||||
|
Text text_current_freq{{UI_POS_X_CENTER(14), UI_POS_Y(3), 112, 16}, ""};
|
||||||
|
Text text_status{{UI_POS_X_CENTER(12), UI_POS_Y(6), 96, 16}, "IDLE"};
|
||||||
|
Button button_start_stop{{UI_POS_X_CENTER(10), UI_POS_Y(8), 80, 32}, "START"};
|
||||||
|
Text text_hits{{UI_POS_X_CENTER(10), UI_POS_Y(11), 80, 16}, "Hits: 0"};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- TAB 2: Freqs View ---
|
||||||
|
class HunterFreqsView : public View {
|
||||||
|
public:
|
||||||
|
HunterFreqsView(Rect parent_rect, SignalHunterAppView& parent);
|
||||||
|
void focus() override;
|
||||||
|
void update_list_count();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SignalHunterAppView& parent_app;
|
||||||
|
Button button_load_file{{UI_POS_X(2), UI_POS_Y(1), 80, 28}, "LOAD FILE"};
|
||||||
|
Button button_clear{{UI_POS_X_RIGHT(12), UI_POS_Y(1), 80, 28}, "CLEAR"};
|
||||||
|
|
||||||
|
Labels labels{
|
||||||
|
{{UI_POS_X(2), UI_POS_Y(4)}, "Dwell Time (ms):", Color::light_grey()}};
|
||||||
|
NumberField field_dwell{{UI_POS_X_RIGHT(8), UI_POS_Y(4)}, 4, {10, 9999}, 10, ' '};
|
||||||
|
|
||||||
|
Text text_loaded_info{{UI_POS_X(2), UI_POS_Y(6), 208, 16}, "Loaded: 0 freqs"};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- TAB 3: Config View ---
|
||||||
|
class HunterConfigView : public View {
|
||||||
|
public:
|
||||||
|
HunterConfigView(Rect parent_rect, SignalHunterAppView& parent);
|
||||||
|
void update_mode_display();
|
||||||
|
void focus() override;
|
||||||
|
void on_show() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SignalHunterAppView& parent_app;
|
||||||
|
|
||||||
|
Button button_mode{
|
||||||
|
{UI_POS_X(2), UI_POS_Y(2), 208, 28},
|
||||||
|
"MODE: SINGLE"};
|
||||||
|
|
||||||
|
RxFrequencyField field_single_freq; // UI_POS_Y(4)
|
||||||
|
|
||||||
|
Labels labels{
|
||||||
|
{{UI_POS_X(2), UI_POS_Y(6)}, "Energy Threshold:", Color::light_grey()},
|
||||||
|
{{UI_POS_X(2), UI_POS_Y(8)}, "Hang-Time (ms):", Color::light_grey()}};
|
||||||
|
|
||||||
|
NumberField field_threshold{{UI_POS_X_RIGHT(8), UI_POS_Y(6)}, 5, {100, 99999}, 100, ' '};
|
||||||
|
NumberField field_hang_time{{UI_POS_X_RIGHT(8), UI_POS_Y(8)}, 4, {10, 5000}, 10, ' '};
|
||||||
|
|
||||||
|
Text text_info_config{
|
||||||
|
{UI_POS_X(2), UI_POS_Y(10), 208, 16},
|
||||||
|
"Restart HUNT after change"};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- MAIN APP VIEW ---
|
||||||
|
class SignalHunterAppView final : public ui::View {
|
||||||
|
public:
|
||||||
|
SignalHunterAppView(ui::NavigationView& nav);
|
||||||
|
~SignalHunterAppView();
|
||||||
|
void focus() override;
|
||||||
|
std::string title() const override { return "SignalHunter"; }
|
||||||
|
ui::NavigationView& get_nav() { return nav_; }
|
||||||
|
|
||||||
|
// Application state shared across all views
|
||||||
|
std::vector<rf::Frequency> frequency_list;
|
||||||
|
uint32_t current_freq_index{0};
|
||||||
|
uint32_t energy_threshold{5000};
|
||||||
|
uint32_t hangtime_ms{500};
|
||||||
|
uint32_t hop_dwell_ms{200};
|
||||||
|
std::string freqman_file{"TARGETS"};
|
||||||
|
bool is_hunting{false};
|
||||||
|
uint32_t trigger_hits{0};
|
||||||
|
rf::Frequency single_frequency{433920000};
|
||||||
|
bool freq_hop_mode{false};
|
||||||
|
|
||||||
|
void send_hunter_config(bool start);
|
||||||
|
HunterMainView* get_main_view() { return view_main.get(); }
|
||||||
|
HunterConfigView* get_config_view() { return view_config.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ui::NavigationView& nav_;
|
||||||
|
|
||||||
|
std::unique_ptr<app_settings::SettingsManager> settings_{};
|
||||||
|
std::unique_ptr<HunterMainView> view_main{};
|
||||||
|
std::unique_ptr<HunterFreqsView> view_freqs{};
|
||||||
|
std::unique_ptr<HunterConfigView> view_config{};
|
||||||
|
std::unique_ptr<TabView> tab_view{};
|
||||||
|
|
||||||
|
std::string current_capture_filename{};
|
||||||
|
std::unique_ptr<CaptureThread> capture_thread{};
|
||||||
|
|
||||||
|
ui::LNAGainField field_lna{{UI_POS_X(0), UI_POS_Y(0)}};
|
||||||
|
ui::VGAGainField field_vga{{UI_POS_X(7), UI_POS_Y(0)}};
|
||||||
|
ui::RFAmpField field_rf_amp{{UI_POS_X(14), UI_POS_Y(0)}};
|
||||||
|
ui::RSSI rssi{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, 4}};
|
||||||
|
|
||||||
|
static constexpr Dim tab_bar_h = 20;
|
||||||
|
|
||||||
|
Rect view_rect{
|
||||||
|
0, UI_POS_Y(2) + 4, UI_POS_MAXWIDTH,
|
||||||
|
screen_height - (UI_POS_Y(2) + 4) - UI_POS_HEIGHT(1)};
|
||||||
|
|
||||||
|
Rect content_rect{
|
||||||
|
0, UI_POS_Y(2) + 4 + tab_bar_h, UI_POS_MAXWIDTH,
|
||||||
|
screen_height - (UI_POS_Y(2) + 4 + tab_bar_h) - UI_POS_HEIGHT(1)};
|
||||||
|
|
||||||
|
void on_hunter_trigger(const HunterTriggerMessage* message);
|
||||||
|
void on_hunter_stop(const HunterStopMessage* message);
|
||||||
|
void start_recording();
|
||||||
|
void stop_recording();
|
||||||
|
|
||||||
|
// timer variables
|
||||||
|
uint32_t hop_timer_ms{0};
|
||||||
|
void on_frame_sync();
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_frame_sync{
|
||||||
|
Message::ID::DisplayFrameSync,
|
||||||
|
[this](const Message* const) {
|
||||||
|
this->on_frame_sync();
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_trigger{
|
||||||
|
Message::ID::HunterTrigger,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
this->on_hunter_trigger(static_cast<const HunterTriggerMessage*>(p));
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_stop{
|
||||||
|
Message::ID::HunterStop,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
this->on_hunter_stop(static_cast<const HunterStopMessage*>(p));
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::signal_hunter
|
||||||
|
|
||||||
|
#endif /*__UI_SIGNAL_HUNTER_H__*/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Mark Thompson
|
* Copyright 2025 Mark Thompson
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Mark Thompson
|
* Copyright 2025 Mark Thompson
|
||||||
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
|
* copyleft 2025 zxkmm
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
|||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 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_tetra_rx.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<TetraRxView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_tetra_rx.application_information"), used)) application_information_t _application_information_tetra_rx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::tetra_rx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
|
||||||
|
/*.app_name = */ "Tetra",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0xE0,
|
||||||
|
0x0F,
|
||||||
|
0x18,
|
||||||
|
0x38,
|
||||||
|
0xE4,
|
||||||
|
0x67,
|
||||||
|
0x7E,
|
||||||
|
0xCE,
|
||||||
|
0xC7,
|
||||||
|
0xCC,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xFF,
|
||||||
|
0x4F,
|
||||||
|
0xBA,
|
||||||
|
0xB2,
|
||||||
|
0x9A,
|
||||||
|
0xEE,
|
||||||
|
0xBA,
|
||||||
|
0xB2,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3B,
|
||||||
|
0xE3,
|
||||||
|
0x73,
|
||||||
|
0x7E,
|
||||||
|
0xC6,
|
||||||
|
0x27,
|
||||||
|
0x1C,
|
||||||
|
0x18,
|
||||||
|
0xF0,
|
||||||
|
0x07,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::orange().v,
|
||||||
|
/*.menu_location = */ app_location_t::RX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_tetrarx*/ {'P', 'T', 'E', 'T'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class BitVector {
|
||||||
|
public:
|
||||||
|
BitVector(uint8_t* p, size_t bits)
|
||||||
|
: data_(p),
|
||||||
|
bits_(bits) {
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool get(size_t bit) const {
|
||||||
|
return (data_[bit >> 3] >>
|
||||||
|
(7 - (bit & 7))) &
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void set(size_t bit, bool value) {
|
||||||
|
if (value)
|
||||||
|
data_[bit >> 3] |=
|
||||||
|
1 << (7 - (bit & 7));
|
||||||
|
else
|
||||||
|
data_[bit >> 3] &=
|
||||||
|
~(1 << (7 - (bit & 7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void xor_bit(size_t bit, bool value) {
|
||||||
|
if (value)
|
||||||
|
data_[bit >> 3] ^=
|
||||||
|
1 << (7 - (bit & 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t size() const {
|
||||||
|
return bits_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* data_;
|
||||||
|
size_t bits_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#include "tetra_crc.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
uint16_t CRC::crc16_itut_bits(
|
||||||
|
const uint8_t* bits,
|
||||||
|
uint32_t n_bits,
|
||||||
|
uint16_t init) {
|
||||||
|
uint16_t crc = init;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < n_bits; i++) {
|
||||||
|
uint8_t bit =
|
||||||
|
(bits[i >> 3] >>
|
||||||
|
(7 - (i & 7))) &
|
||||||
|
1;
|
||||||
|
|
||||||
|
crc ^= uint16_t(bit) << 15;
|
||||||
|
|
||||||
|
if (crc & 0x8000) {
|
||||||
|
crc =
|
||||||
|
(crc << 1) ^
|
||||||
|
0x1021;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
crc &= 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class CRC {
|
||||||
|
public:
|
||||||
|
static constexpr uint16_t CRC_OK = 0x1d0f;
|
||||||
|
|
||||||
|
static uint16_t crc16_itut_bits(
|
||||||
|
const uint8_t* bits,
|
||||||
|
uint32_t n_bits,
|
||||||
|
uint16_t init = 0xffff);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#include "tetra_descrambler.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
static inline uint8_t lfsr_step(uint32_t& s) {
|
||||||
|
uint8_t b = 0;
|
||||||
|
|
||||||
|
b ^= (s >> (32 - 32)) & 1;
|
||||||
|
b ^= (s >> (32 - 26)) & 1;
|
||||||
|
b ^= (s >> (32 - 23)) & 1;
|
||||||
|
b ^= (s >> (32 - 22)) & 1;
|
||||||
|
b ^= (s >> (32 - 16)) & 1;
|
||||||
|
b ^= (s >> (32 - 12)) & 1;
|
||||||
|
b ^= (s >> (32 - 11)) & 1;
|
||||||
|
b ^= (s >> (32 - 10)) & 1;
|
||||||
|
b ^= (s >> (32 - 8)) & 1;
|
||||||
|
b ^= (s >> (32 - 7)) & 1;
|
||||||
|
b ^= (s >> (32 - 5)) & 1;
|
||||||
|
b ^= (s >> (32 - 4)) & 1;
|
||||||
|
b ^= (s >> (32 - 2)) & 1;
|
||||||
|
b ^= (s >> (32 - 1)) & 1;
|
||||||
|
|
||||||
|
s = (s >> 1) | (uint32_t(b) << 31);
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Descrambler::generate(
|
||||||
|
uint32_t init,
|
||||||
|
uint8_t* seq,
|
||||||
|
size_t bits) {
|
||||||
|
uint32_t s = init;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bits; i++)
|
||||||
|
seq[i] = lfsr_step(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Descrambler::descramble(
|
||||||
|
BitVector& bits,
|
||||||
|
uint32_t init) {
|
||||||
|
uint32_t s = init;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bits.size(); i++)
|
||||||
|
bits.xor_bit(i, lfsr_step(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Descrambler::dmo_scramb_init(
|
||||||
|
uint32_t mni,
|
||||||
|
uint32_t src) {
|
||||||
|
uint32_t init =
|
||||||
|
(src & 0xFFFFFF) |
|
||||||
|
((mni & 0x3F) << 24);
|
||||||
|
|
||||||
|
init <<= 2;
|
||||||
|
init |= 3;
|
||||||
|
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "tetra_bits.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class Descrambler {
|
||||||
|
public:
|
||||||
|
static constexpr uint32_t SCRAMB_INIT_BSCH = 0x00000003;
|
||||||
|
|
||||||
|
static void descramble(
|
||||||
|
BitVector& bits,
|
||||||
|
uint32_t init = SCRAMB_INIT_BSCH);
|
||||||
|
|
||||||
|
static void generate(
|
||||||
|
uint32_t init,
|
||||||
|
uint8_t* sequence,
|
||||||
|
size_t bits);
|
||||||
|
|
||||||
|
static uint32_t dmo_scramb_init(
|
||||||
|
uint32_t mni,
|
||||||
|
uint32_t src);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include "tetra_interleave.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
void Interleave::block_deinterleave(
|
||||||
|
BitVector& in,
|
||||||
|
BitVector& out,
|
||||||
|
uint32_t K,
|
||||||
|
uint32_t a) {
|
||||||
|
for (uint32_t i = 1; i <= K; i++) {
|
||||||
|
uint32_t k =
|
||||||
|
1 +
|
||||||
|
((a * i) % K);
|
||||||
|
|
||||||
|
out.set(
|
||||||
|
i - 1,
|
||||||
|
in.get(k - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tetra_bits.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class Interleave {
|
||||||
|
public:
|
||||||
|
static void block_deinterleave(
|
||||||
|
BitVector& in,
|
||||||
|
BitVector& out,
|
||||||
|
uint32_t K,
|
||||||
|
uint32_t a);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include "tetra_rcpc.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
static constexpr uint8_t P_RATE_2_3[3] = {1, 2, 5};
|
||||||
|
|
||||||
|
static constexpr uint32_t T_RATE_2_3 = 3;
|
||||||
|
static constexpr uint32_t PERIOD = 8;
|
||||||
|
|
||||||
|
void RCPC::depuncture_2_3(BitVector& in, uint8_t* out, uint32_t in_bits) {
|
||||||
|
const uint32_t mother_bits = ((in_bits + T_RATE_2_3 - 1) / T_RATE_2_3) * PERIOD + PERIOD;
|
||||||
|
memset(out, ERASED, mother_bits);
|
||||||
|
for (uint32_t j = 1; j <= in_bits; j++) {
|
||||||
|
uint32_t i = j;
|
||||||
|
uint32_t k = PERIOD * ((i - 1) / T_RATE_2_3);
|
||||||
|
k += P_RATE_2_3[(i - 1) % T_RATE_2_3];
|
||||||
|
if (k >= 1 && k <= mother_bits) {
|
||||||
|
out[k - 1] = in.get(j - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "tetra_bits.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class RCPC {
|
||||||
|
public:
|
||||||
|
static constexpr uint8_t ERASED = 0xff;
|
||||||
|
|
||||||
|
static void depuncture_2_3(
|
||||||
|
BitVector& in,
|
||||||
|
uint8_t* out,
|
||||||
|
uint32_t in_bits);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include "tetra_viterbi.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
int Viterbi::decode_cch(
|
||||||
|
const uint8_t* mother,
|
||||||
|
uint32_t n_info,
|
||||||
|
uint8_t* bits,
|
||||||
|
uint8_t* trace_buffer) { // Puffer by caller
|
||||||
|
|
||||||
|
// Store these on stack, to avoid flash space usage
|
||||||
|
const uint8_t next_state[16][2] = {
|
||||||
|
{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}};
|
||||||
|
const uint8_t next_output[16][2] = {
|
||||||
|
{0, 15}, {11, 4}, {6, 9}, {13, 2}, {5, 10}, {14, 1}, {3, 12}, {8, 7}, {15, 0}, {4, 11}, {9, 6}, {2, 13}, {10, 5}, {1, 14}, {12, 3}, {7, 8}};
|
||||||
|
|
||||||
|
const uint16_t INF = 0x3fff;
|
||||||
|
uint16_t cost[16];
|
||||||
|
uint16_t new_cost[16];
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
for (int i = 0; i < 16; i++) cost[i] = INF;
|
||||||
|
cost[0] = 0;
|
||||||
|
|
||||||
|
for (uint32_t step = 0; step < n_info; step++) {
|
||||||
|
for (int i = 0; i < 16; i++) new_cost[i] = INF;
|
||||||
|
const uint8_t* rx = &mother[step * 4];
|
||||||
|
|
||||||
|
for (int prev = 0; prev < 16; prev++) {
|
||||||
|
if (cost[prev] >= INF) continue;
|
||||||
|
|
||||||
|
for (int bit = 0; bit < 2; bit++) {
|
||||||
|
const uint8_t pattern = next_output[prev][bit];
|
||||||
|
const uint8_t next = next_state[prev][bit];
|
||||||
|
|
||||||
|
uint16_t d = 0;
|
||||||
|
for (int b = 0; b < 4; b++) {
|
||||||
|
if (rx[b] != 0xff && rx[b] != ((pattern >> (3 - b)) & 1)) d++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t total = cost[prev] + d;
|
||||||
|
if (total < new_cost[next]) {
|
||||||
|
new_cost[next] = total;
|
||||||
|
trace_buffer[step * 16 + next] = (prev << 1) | bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(cost, new_cost, sizeof(cost));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traceback
|
||||||
|
uint8_t best_state = 0;
|
||||||
|
for (int i = 1; i < 16; i++) {
|
||||||
|
if (cost[i] < cost[best_state]) best_state = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t state = best_state;
|
||||||
|
memset(bits, 0, (n_info + 7) >> 3);
|
||||||
|
|
||||||
|
for (int step = (int)n_info - 1; step >= 0; step--) {
|
||||||
|
uint8_t packed = trace_buffer[step * 16 + state];
|
||||||
|
uint8_t prev = (packed >> 1) & 0x0F;
|
||||||
|
uint8_t bit = packed & 0x01;
|
||||||
|
|
||||||
|
if (bit) bits[step >> 3] |= (1 << (7 - (step & 7)));
|
||||||
|
state = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost[best_state];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ui::external_app::tetra_rx {
|
||||||
|
|
||||||
|
class Viterbi {
|
||||||
|
public:
|
||||||
|
int decode_cch(
|
||||||
|
const uint8_t* mother,
|
||||||
|
uint32_t n_info,
|
||||||
|
uint8_t* bits,
|
||||||
|
uint8_t* trace_buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::tetra_rx
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user