mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 18:19:33 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 409242507c | |||
| 0a8194fa10 | |||
| eaac40df3a | |||
| 58b488b840 | |||
| 1676702a09 | |||
| 3943848add | |||
| 831dbeaab5 | |||
| 49e719ded8 | |||
| c2d2409bff | |||
| 485f57d86b | |||
| 0e6f303eec | |||
| ad94ae05c1 | |||
| 89dc8c0463 | |||
| 77640c5cc0 | |||
| cdc8cfeb7d | |||
| 4740df2e2c | |||
| ec0f45a488 | |||
| 9d7e06c255 | |||
| 4b93e78dd9 | |||
| 5ced8c90d3 | |||
| 9d22711368 | |||
| 23e6295dd2 | |||
| bbd75ab7ef | |||
| 7508fee62c |
@@ -9,7 +9,7 @@ assignees: ''
|
||||
|
||||
----
|
||||
|
||||
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/eried/portapack-mayhem/releases)
|
||||
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases)
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ assignees: ''
|
||||
|
||||
----
|
||||
Before creating this issue, **do the following**:
|
||||
* Read the Wiki on booting: https://github.com/eried/portapack-mayhem/wiki/Won't-boot
|
||||
* Read: https://github.com/eried/portapack-havoc/wiki/Update-firmware
|
||||
* Read the Wiki on booting: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
|
||||
* Read: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
|
||||
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
|
||||
* (if you are not in Windows) also check: https://www.youtube.com/watch?v=kjFB58Y1TAo
|
||||
|
||||
@@ -27,12 +27,12 @@ Steps to reproduce the behavior:
|
||||
|
||||
**My Hardware**
|
||||
Please specify what PortaPack hardware version you are using.
|
||||
You can find the list of versions here: https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions
|
||||
You can find the list of versions here: https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
|
||||
|
||||
**Affected versions**
|
||||
Please tell us what version you are running.
|
||||
If your device is still functional, try the latest nightly release before submitting this.
|
||||
You can find the latest nightly version here https://github.com/eried/portapack-mayhem/releases
|
||||
You can find the latest nightly version here https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
|
||||
**Were you able to update the firmware before?**
|
||||
Things might be confusing the first time, please check the video available on the link above.
|
||||
|
||||
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone
|
||||
|
||||
# Set up your personal access token and the repository details
|
||||
token = os.environ.get('GH_TOKEN')
|
||||
repo_owner = "eried"
|
||||
repo_name = "portapack-mayhem"
|
||||
repo_owner = "portapack-mayhem"
|
||||
repo_name = "mayhem-firmware"
|
||||
|
||||
|
||||
def print_stable_changelog(previous_sha):
|
||||
|
||||
@@ -54,10 +54,10 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -38,10 +38,10 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -71,15 +71,15 @@ jobs:
|
||||
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
**Stable release - ${{ steps.version.outputs.version }}**
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/eried/portapack-mayhem/blob/master/README.md) for details.
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
|
||||
## Release notes
|
||||
### Revision (${{ steps.version.outputs.version }}):
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
|
||||
## Installation
|
||||
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
|
||||
Check the [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware) for details how to upgrade.
|
||||
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
|
||||
|
||||
### MicroSD card files
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
> [!IMPORTANT]
|
||||
> *Shortly*, this repository **will be moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem/portapack-mayhem](https://github.com/portapack-mayhem/portapack-mayhem). Please keep this in mind to **update your links** accordingly.
|
||||
> this repository **has just been moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please keep this in mind to **update your links** accordingly.
|
||||
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://travis-ci.com/eried/portapack-mayhem) [](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/eried/portapack-mayhem/releases) [](https://github.com/eried/portapack-mayhem/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_front.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_inside.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview#portapack-internals)
|
||||
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
|
||||
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
||||
|
||||
# What is this?
|
||||
|
||||
@@ -21,11 +21,11 @@ For people familiar with the [Flipper Zero](https://github.com/flipperdevices/fl
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) is always welcomed and appreciated.
|
||||
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) is always welcomed and appreciated.
|
||||
|
||||
## What to buy?
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
||||
|
||||
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
|
||||
|
||||
@@ -35,33 +35,33 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||

|
||||
|
||||
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
|
||||
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
|
||||
|
||||

|
||||
|
||||
## Where is the latest version?
|
||||
|
||||
The current **stable release** is on the [](https://github.com/eried/portapack-mayhem/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/eried/portapack-mayhem/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
|
||||
You can write [documentation](https://github.com/eried/portapack-mayhem/wiki), fix bugs and [answer issues](https://github.com/eried/portapack-mayhem/issues) or add new functionality. Please check the following [guide](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) with details.
|
||||
You can write [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki), fix bugs and [answer issues](https://github.com/portapack-mayhem/mayhem-firmware/issues) or add new functionality. Please check the following [guide](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) with details.
|
||||
|
||||
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/eried/portapack-mayhem/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
|
||||
[](https://github.com/eried/portapack-mayhem/graphs/contributors)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors)
|
||||
|
||||
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
||||
|
||||
## What if I really want something specific?
|
||||
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
|
||||
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md).
|
||||
|
||||
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
|
||||
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose) or [choosing](https://github.com/portapack-mayhem/mayhem-firmware/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
|
||||
|
||||
<del>Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).</del>
|
||||
|
||||
Bountysource has not been reliable lately, so until this changes, please **DO NOT** post a bounty there. Go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||
|
||||
## What if I need help?
|
||||
First, check the [documentation](https://github.com/eried/portapack-mayhem/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/eried/portapack-mayhem/issues/new/choose).
|
||||
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
|
||||
|
||||
You can reach the [official community](https://www.facebook.com/groups/177623356165819) in Facebook, and our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; URL=https://github.com/eried/portapack-mayhem/" />
|
||||
<meta http-equiv="refresh" content="0; URL=https://github.com/portapack-mayhem/mayhem-firmware/" />
|
||||
</head>
|
||||
<body>
|
||||
<p>If you are not redirected, <a href="https://github.com/eried/portapack-mayhem/">click here</a>.</p>
|
||||
<p>If you are not redirected, <a href="https://github.com/portapack-mayhem/mayhem-firmware/">click here</a>.</p>
|
||||
</body>
|
||||
|
||||
@@ -203,6 +203,7 @@ set(CPPSRC
|
||||
metadata_file.cpp
|
||||
portapack.cpp
|
||||
usb_serial_shell.cpp
|
||||
usb_serial_shell_filesystem.cpp
|
||||
usb_serial_event.cpp
|
||||
usb_serial_thread.cpp
|
||||
usb_serial.cpp
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
using namespace tonekey;
|
||||
using namespace portapack;
|
||||
@@ -40,6 +41,7 @@ void SoundBoardView::stop() {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
audio::output::stop();
|
||||
transmitter_model.disable();
|
||||
tx_view.set_transmitting(false);
|
||||
|
||||
@@ -123,6 +125,8 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
transmitter_model.enable();
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -227,6 +231,7 @@ SoundBoardView::SoundBoardView(
|
||||
//&text_title,
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&field_volume,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -274,7 +279,6 @@ SoundBoardView::SoundBoardView(
|
||||
|
||||
SoundBoardView::~SoundBoardView() {
|
||||
stop();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class SoundBoardView : public View {
|
||||
void on_select_entry();
|
||||
|
||||
Labels labels{
|
||||
//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
|
||||
{{24 * 8, 180}, "Vol:", Color::light_grey()},
|
||||
{{0, 180}, "Key:", Color::light_grey()}};
|
||||
|
||||
Button button_next_page{
|
||||
@@ -122,10 +122,13 @@ class SoundBoardView : public View {
|
||||
};*/
|
||||
|
||||
OptionsField options_tone_key{
|
||||
{32, 180},
|
||||
{4 * 8, 180},
|
||||
18,
|
||||
{}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 180}};
|
||||
|
||||
Checkbox check_loop{
|
||||
{0, 25 * 8 + 4},
|
||||
4,
|
||||
|
||||
@@ -19,7 +19,7 @@ void AboutView::update() {
|
||||
switch (++frame) {
|
||||
case 1:
|
||||
// TODO: Generate this automatically from github
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
|
||||
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
|
||||
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
|
||||
console.writeln("eried,euquiq,gregoryfenton");
|
||||
console.writeln("johnelder,jwetzell,nnemanjan00");
|
||||
@@ -42,7 +42,7 @@ void AboutView::update() {
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
|
||||
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
|
||||
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
|
||||
console.writeln("furrtek,mrmookie,NotPike");
|
||||
console.writeln("mjwaxios,ImDroided,Giorgiofox");
|
||||
@@ -55,7 +55,7 @@ void AboutView::update() {
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
|
||||
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
|
||||
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
|
||||
console.writeln("jboone,argilo");
|
||||
console.writeln("");
|
||||
|
||||
@@ -318,6 +318,17 @@ bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
|
||||
return markerStored == MARKER_STORED;
|
||||
}
|
||||
|
||||
void ADSBRxDetailsView::on_gps(const GPSPosDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
|
||||
}
|
||||
void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_orientation(msg->angle);
|
||||
}
|
||||
|
||||
void ADSBRxDetailsView::refresh_ui() {
|
||||
auto age = entry_.age;
|
||||
if (age < 60)
|
||||
|
||||
@@ -271,6 +271,8 @@ class ADSBRxDetailsView : public View {
|
||||
|
||||
private:
|
||||
void refresh_ui();
|
||||
void on_gps(const GPSPosDataMessage* msg);
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
|
||||
GeoMapView* geomap_view_{nullptr};
|
||||
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
|
||||
@@ -330,6 +332,19 @@ class ADSBRxDetailsView : public View {
|
||||
Button button_see_map{
|
||||
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
|
||||
"See on map"};
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||
this->on_gps(message);
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_orientation{
|
||||
Message::ID::OrientationData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||
this->on_orientation(message);
|
||||
}};
|
||||
};
|
||||
|
||||
/* Main ADSB application view and message dispatch. */
|
||||
|
||||
@@ -48,7 +48,7 @@ void DfuMenu::paint(Painter& painter) {
|
||||
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
|
||||
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||
|
||||
@@ -94,23 +94,25 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
&text_info_line_7,
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10});
|
||||
&text_info_line_10,
|
||||
&text_info_line_11});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
||||
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
||||
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
||||
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
||||
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||
text_info_line_4.set(to_string_dec_uint(((uint32_t)shared_memory.m4_performance_counter) * 100 / 127, 10));
|
||||
text_info_line_5.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_8.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 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_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 10 + 2;
|
||||
constexpr auto lines = 11 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -83,13 +83,15 @@ class DfuMenu2 : public View {
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "RX Satu%:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()},
|
||||
};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -101,6 +103,7 @@ class DfuMenu2 : public View {
|
||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * 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_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -275,7 +275,7 @@ ScannerView::~ScannerView() {
|
||||
}
|
||||
|
||||
void ScannerView::show_max_index() { // show total number of freqs to scan
|
||||
field_current_index.set_text("---");
|
||||
field_current_index.set_text("<->");
|
||||
|
||||
if (entries.size() == FREQMAN_MAX_PER_FILE) {
|
||||
text_max_index.set_style(&Styles::red);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_touch_calibration.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
@@ -628,6 +629,34 @@ void SetEncoderDialView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* AppSettingsView ************************************/
|
||||
|
||||
AppSettingsView::AppSettingsView(
|
||||
NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
|
||||
auto path = (std::filesystem::path)SETTINGS_DIR / entry.path();
|
||||
|
||||
menu_view.add_item({path.filename().string().substr(0, 26),
|
||||
ui::Color::dark_cyan(),
|
||||
&bitmap_icon_file_text,
|
||||
[this, path](KeyEvent) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void AppSettingsView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
@@ -635,6 +664,7 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
|
||||
|
||||
@@ -564,6 +564,23 @@ class SetPersistentMemoryView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
class AppSettingsView : public View {
|
||||
public:
|
||||
AppSettingsView(NavigationView& nav);
|
||||
std::string title() const override { return "App Settings"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Labels labels{
|
||||
{{0, 4}, "Select file to edit:", Color::white()}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -90,7 +90,7 @@ SondeView::SondeView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
nav.push<GeoMapView>(
|
||||
geomap_view_ = nav.push<GeoMapView>(
|
||||
sonde_id,
|
||||
gps_info.alt,
|
||||
GeoPos::alt_unit::METERS,
|
||||
@@ -98,6 +98,9 @@ SondeView::SondeView(NavigationView& nav)
|
||||
gps_info.lat,
|
||||
gps_info.lon,
|
||||
999); // set a dummy heading out of range to draw a cross...probably not ideal?
|
||||
nav.set_on_pop([this]() {
|
||||
geomap_view_ = nullptr;
|
||||
});
|
||||
};
|
||||
|
||||
logger = std::make_unique<SondeLogger>();
|
||||
@@ -124,6 +127,17 @@ SondeView::~SondeView() {
|
||||
audio::output::stop();
|
||||
}
|
||||
|
||||
void SondeView::on_gps(const GPSPosDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
|
||||
}
|
||||
void SondeView::on_orientation(const OrientationDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_orientation(msg->angle, true);
|
||||
}
|
||||
|
||||
void SondeView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ class SondeView : public View {
|
||||
|
||||
SondeView(NavigationView& nav);
|
||||
~SondeView();
|
||||
SondeView(const SondeView& other) = delete;
|
||||
SondeView& operator=(const SondeView& other) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -173,6 +175,8 @@ class SondeView : public View {
|
||||
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
|
||||
"See on map"};
|
||||
|
||||
GeoMapView* geomap_view_{nullptr};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::SondePacket,
|
||||
[this](Message* const p) {
|
||||
@@ -181,6 +185,21 @@ class SondeView : public View {
|
||||
this->on_packet(packet);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||
this->on_gps(message);
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_orientation{
|
||||
Message::ID::OrientationData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||
this->on_orientation(message);
|
||||
}};
|
||||
|
||||
void on_gps(const GPSPosDataMessage* msg);
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
void on_packet(const sonde::Packet& packet);
|
||||
char* float_to_char(float x, char* p);
|
||||
};
|
||||
|
||||
@@ -113,7 +113,6 @@ void EventDispatcher::run() {
|
||||
while (is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
portapack::usb_serial.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +150,8 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
*(uint32_t*)&shared_memory.bb_data.data[4]);
|
||||
}
|
||||
|
||||
handle_shell();
|
||||
|
||||
if (events & EVT_MASK_APPLICATION) {
|
||||
handle_application_queue();
|
||||
}
|
||||
@@ -163,6 +164,10 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_USB) {
|
||||
handle_usb();
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_SWITCHES) {
|
||||
handle_switches();
|
||||
}
|
||||
@@ -216,6 +221,30 @@ void EventDispatcher::handle_rtc_tick() {
|
||||
portapack::persistent_memory::cache::persist();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_usb() {
|
||||
portapack::usb_serial.dispatch();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_shell() {
|
||||
if (waiting_for_shellmode) {
|
||||
waiting_for_shellmode = false;
|
||||
shellmode_active = true;
|
||||
while (shellmode_active) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
if (injected_touch_event != nullptr) {
|
||||
on_touch_event(*injected_touch_event);
|
||||
injected_touch_event = nullptr;
|
||||
}
|
||||
|
||||
if (injected_keyboard_event != nullptr) {
|
||||
on_keyboard_event(*injected_keyboard_event);
|
||||
injected_keyboard_event = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent event) {
|
||||
if (!w->hidden()) {
|
||||
// To achieve reverse depth ordering (last object drawn is
|
||||
@@ -239,11 +268,17 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
}
|
||||
|
||||
void EventDispatcher::emulateTouch(ui::TouchEvent event) {
|
||||
on_touch_event(event);
|
||||
injected_touch_event = &event;
|
||||
while (injected_touch_event != nullptr) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::emulateKeyboard(ui::KeyboardEvent event) {
|
||||
on_keyboard_event(event);
|
||||
injected_keyboard_event = &event;
|
||||
while (injected_keyboard_event != nullptr) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::on_keyboard_event(ui::KeyboardEvent event) {
|
||||
@@ -283,6 +318,8 @@ ui::Widget* EventDispatcher::getFocusedWidget() {
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_lcd_frame_sync() {
|
||||
bool waiting_for_frame = this->waiting_for_frame;
|
||||
|
||||
DisplayFrameSyncMessage message;
|
||||
message_map.send(&message);
|
||||
|
||||
@@ -290,6 +327,28 @@ void EventDispatcher::handle_lcd_frame_sync() {
|
||||
painter.paint_widget_tree(top_widget);
|
||||
|
||||
portapack::backlight()->on();
|
||||
|
||||
if (waiting_for_frame)
|
||||
this->waiting_for_frame = false;
|
||||
}
|
||||
|
||||
void EventDispatcher::wait_finish_frame() {
|
||||
waiting_for_frame = true;
|
||||
while (waiting_for_frame) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::enter_shell_working_mode() {
|
||||
waiting_for_shellmode = true;
|
||||
|
||||
while (waiting_for_shellmode) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::exit_shell_working_mode() {
|
||||
shellmode_active = false;
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_switches() {
|
||||
@@ -351,6 +410,9 @@ void EventDispatcher::handle_encoder() {
|
||||
|
||||
const uint32_t encoder_now = get_encoder_position();
|
||||
const int32_t delta = static_cast<int32_t>(encoder_now - encoder_last);
|
||||
if (delta == 0)
|
||||
return;
|
||||
|
||||
encoder_last = encoder_now;
|
||||
const auto event = static_cast<ui::EncoderEvent>(delta);
|
||||
event_bubble_encoder(event);
|
||||
|
||||
@@ -45,6 +45,7 @@ constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4);
|
||||
constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5);
|
||||
constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6);
|
||||
constexpr auto EVT_MASK_LOCAL = EVENT_MASK(7);
|
||||
constexpr auto EVT_MASK_USB = EVENT_MASK(8);
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
@@ -89,6 +90,10 @@ class EventDispatcher {
|
||||
void emulateTouch(ui::TouchEvent event);
|
||||
void emulateKeyboard(ui::KeyboardEvent event);
|
||||
|
||||
void wait_finish_frame();
|
||||
void enter_shell_working_mode();
|
||||
void exit_shell_working_mode();
|
||||
|
||||
ui::Widget* getTopWidget();
|
||||
ui::Widget* getFocusedWidget();
|
||||
|
||||
@@ -104,6 +109,11 @@ class EventDispatcher {
|
||||
bool sd_card_present = false;
|
||||
static bool display_sleep;
|
||||
bool in_key_event = false;
|
||||
volatile bool waiting_for_frame = false;
|
||||
volatile bool waiting_for_shellmode = false;
|
||||
volatile bool shellmode_active = false;
|
||||
ui::TouchEvent* volatile injected_touch_event = nullptr;
|
||||
ui::KeyboardEvent* volatile injected_keyboard_event = nullptr;
|
||||
|
||||
eventmask_t wait();
|
||||
void dispatch(const eventmask_t events);
|
||||
@@ -111,6 +121,8 @@ class EventDispatcher {
|
||||
void handle_application_queue();
|
||||
void handle_local_queue();
|
||||
void handle_rtc_tick();
|
||||
void handle_usb();
|
||||
void handle_shell();
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
|
||||
|
||||
|
||||
+21
-7
@@ -205,11 +205,13 @@ class Sprite {
|
||||
byte who;
|
||||
byte _speed;
|
||||
byte dir;
|
||||
byte lastDir;
|
||||
byte phase;
|
||||
|
||||
// Sprite bits
|
||||
byte palette2; // 4->16 color map index
|
||||
byte bits; // index of sprite bits
|
||||
|
||||
signed char sy;
|
||||
|
||||
void Init(const byte* s) {
|
||||
@@ -274,9 +276,17 @@ class Sprite {
|
||||
f = pgm_read_byte(_pacLeftAnim + f);
|
||||
else if (dir == MRight)
|
||||
f = pgm_read_byte(_pacRightAnim + f);
|
||||
else
|
||||
else if (dir == MDown || dir == MUp)
|
||||
f = pgm_read_byte(_pacVAnim + f);
|
||||
if (dir == MUp)
|
||||
else if (dir == MStopped) {
|
||||
if (lastDir == MLeft)
|
||||
f = pgm_read_byte(_pacLeftAnim + f);
|
||||
else if (lastDir == MRight)
|
||||
f = pgm_read_byte(_pacRightAnim + f);
|
||||
else if (lastDir == MDown || lastDir == MUp)
|
||||
f = pgm_read_byte(_pacVAnim + f);
|
||||
}
|
||||
if ((dir == MUp) || (dir == MStopped && lastDir == MUp))
|
||||
sy = -1;
|
||||
bits = f + PACMANSPRITE;
|
||||
}
|
||||
@@ -838,15 +848,19 @@ class Playfield {
|
||||
else if (DEMO == 0 && s->who == PACMAN && choice[3] < 0x7FFF && but_RIGHT)
|
||||
dir = MRight;
|
||||
|
||||
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp)
|
||||
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp) {
|
||||
dir = MUp;
|
||||
else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft)
|
||||
s->lastDir = MUp;
|
||||
} else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft) {
|
||||
dir = MLeft;
|
||||
else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown)
|
||||
s->lastDir = MLeft;
|
||||
} else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown) {
|
||||
dir = MDown;
|
||||
else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight)
|
||||
s->lastDir = MDown;
|
||||
} else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight) {
|
||||
dir = MRight;
|
||||
else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
|
||||
s->lastDir = MRight;
|
||||
} else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
|
||||
// Don't choose opposite of current direction?
|
||||
|
||||
int16_t dist = choice[4 - dir]; // favor current direction
|
||||
|
||||
@@ -116,16 +116,8 @@ void set_direction(const rf::Direction new_direction) {
|
||||
/* TODO: Refactor all the various "Direction" enumerations into one. */
|
||||
/* TODO: Only make changes if direction changes, but beware of clock enabling. */
|
||||
|
||||
// Hack to fix the CPLD (clocking ?) bug: toggle CPLD SRAM overlay depending on new direction.
|
||||
// Use CPLD's EEPROM config when transmitting
|
||||
// Use the SRAM overlay when receiving
|
||||
if (direction != new_direction) {
|
||||
if (new_direction == rf::Direction::Transmit)
|
||||
hackrf::cpld::init_from_eeprom();
|
||||
else
|
||||
// Prevents ghosting when switching back to RX from TX mode.
|
||||
hackrf::cpld::load_sram_no_verify();
|
||||
}
|
||||
// Prevents ghosting when switching back to RX from TX mode.
|
||||
hackrf::cpld::load_sram_no_verify();
|
||||
|
||||
direction = new_direction;
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ void GeoMap::paint(Painter& painter) {
|
||||
// Draw the other markers
|
||||
draw_markers(painter);
|
||||
draw_scale(painter);
|
||||
draw_mypos();
|
||||
markerListUpdated = false;
|
||||
set_clean();
|
||||
}
|
||||
@@ -447,6 +448,41 @@ void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_mypos() {
|
||||
const auto r = screen_rect();
|
||||
if (my_lat >= 200 || my_lon >= 200) return; // invalid
|
||||
int x = (map_width * (my_lon + 180) / 360) - x_pos;
|
||||
double lat_rad = sin(my_lat * pi / 180);
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
auto color = Color::yellow();
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
} else if (map_zoom < 0) {
|
||||
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
||||
}
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
display.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
|
||||
} else if (my_angle < 360) {
|
||||
// if we have a valid angle draw bearing
|
||||
draw_bearing(itemPoint, my_angle, 10, color);
|
||||
} else {
|
||||
// draw a small cross
|
||||
display.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::clear_markers() {
|
||||
markerListLen = 0;
|
||||
}
|
||||
@@ -473,6 +509,21 @@ MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
|
||||
my_lat = lat;
|
||||
my_lon = lon;
|
||||
my_altitude = altitude;
|
||||
markerListUpdated = true;
|
||||
set_dirty();
|
||||
}
|
||||
void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
my_angle = angle;
|
||||
if (refresh) {
|
||||
markerListUpdated = true;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMapView::focus() {
|
||||
geopos.focus();
|
||||
|
||||
@@ -480,6 +531,13 @@ void GeoMapView::focus() {
|
||||
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT);
|
||||
}
|
||||
|
||||
void GeoMapView::update_my_position(float lat, float lon, int32_t altitude) {
|
||||
geomap.update_my_position(lat, lon, altitude);
|
||||
}
|
||||
void GeoMapView::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
geomap.update_my_orientation(angle, refresh);
|
||||
}
|
||||
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed) {
|
||||
if (geomap.manual_panning()) {
|
||||
geomap.set_dirty();
|
||||
|
||||
@@ -185,6 +185,9 @@ class GeoMap : public Widget {
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void update_my_position(float lat, float lon, int32_t altitude);
|
||||
void update_my_orientation(uint16_t angle, bool refresh = false);
|
||||
|
||||
bool init();
|
||||
void set_mode(GeoMapMode mode);
|
||||
void set_manual_panning(bool v);
|
||||
@@ -212,6 +215,7 @@ class GeoMap : public Widget {
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
void draw_mypos();
|
||||
void map_read_line(ui::Color* buffer, uint16_t pixels);
|
||||
|
||||
bool manual_panning_{false};
|
||||
@@ -233,6 +237,12 @@ class GeoMap : public Widget {
|
||||
uint16_t angle_{};
|
||||
std::string tag_{};
|
||||
|
||||
// the portapack's position data ( for example injected from serial )
|
||||
float my_lat{200};
|
||||
float my_lon{200};
|
||||
int32_t my_altitude{0};
|
||||
uint16_t my_angle{400};
|
||||
|
||||
int markerListLen{0};
|
||||
GeoMarker markerList[NumMarkerListElements];
|
||||
bool markerListUpdated{false};
|
||||
@@ -267,6 +277,8 @@ class GeoMapView : public View {
|
||||
void focus() override;
|
||||
|
||||
void update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed = 0);
|
||||
void update_my_position(float lat, float lon, int32_t altitude);
|
||||
void update_my_orientation(uint16_t angle, bool refresh = false);
|
||||
|
||||
std::string title() const override { return "Map view"; };
|
||||
|
||||
|
||||
@@ -6,6 +6,44 @@ namespace ui {
|
||||
|
||||
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
|
||||
|
||||
// iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info.
|
||||
/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback) {
|
||||
if (!callback) return;
|
||||
if (sd_card::status() != sd_card::Status::Mounted)
|
||||
return;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(u"APPS", u"*.ppma")) {
|
||||
auto filePath = u"/APPS/" + entry.path();
|
||||
File app;
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
continue;
|
||||
|
||||
application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(application_information_t));
|
||||
if (!readResult)
|
||||
continue;
|
||||
|
||||
if (application_information.header_version != CURRENT_HEADER_VERSION)
|
||||
continue;
|
||||
|
||||
bool versionMatches = VERSION_MD5 == application_information.app_version;
|
||||
if (!versionMatches) continue;
|
||||
// here the app is startable and good.
|
||||
std::string appshortname = filePath.filename().string();
|
||||
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
|
||||
// Remove the ".ppma" suffix
|
||||
appshortname = appshortname.substr(0, appshortname.size() - 5);
|
||||
}
|
||||
AppInfoConsole info{
|
||||
.appCallName = appshortname.c_str(),
|
||||
.appFriendlyName = reinterpret_cast<char*>(&application_information.app_name[0]),
|
||||
.appLocation = application_information.menu_location};
|
||||
callback(info);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ std::vector<GridItem> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
bitmaps.clear();
|
||||
|
||||
@@ -47,7 +85,9 @@ namespace ui {
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
run_external_app(nav, filePath);
|
||||
if (!run_external_app(nav, filePath)) {
|
||||
nav.display_modal("Error", "The .ppma file in your APPS\nfolder can't be read. Please\nupdate your SD Card content.");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
gridItem.color = Color::light_grey();
|
||||
@@ -65,19 +105,19 @@ namespace ui {
|
||||
return external_apps;
|
||||
}
|
||||
|
||||
/* static */ void ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
/* static */ bool ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
File app;
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
chDbgPanic("file gone");
|
||||
return false;
|
||||
|
||||
application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(application_information_t));
|
||||
|
||||
if (!readResult)
|
||||
chDbgPanic("no data");
|
||||
return false;
|
||||
|
||||
app.seek(0);
|
||||
|
||||
@@ -93,7 +133,7 @@ namespace ui {
|
||||
|
||||
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
|
||||
if (!readResult)
|
||||
chDbgPanic("read error");
|
||||
return false;
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
@@ -114,7 +154,7 @@ namespace ui {
|
||||
|
||||
readResult = app.read(target_memory, bytes_to_read);
|
||||
if (!readResult)
|
||||
chDbgPanic("read error #2");
|
||||
return false;
|
||||
|
||||
if (readResult.value() != bytes_to_read)
|
||||
break;
|
||||
@@ -126,7 +166,7 @@ namespace ui {
|
||||
|
||||
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
|
||||
if (!readResult)
|
||||
chDbgPanic("read error #3");
|
||||
return false;
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
@@ -134,6 +174,7 @@ namespace ui {
|
||||
}
|
||||
|
||||
application_information.externalAppEntry(nav);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -54,11 +54,11 @@ class ExternalItemsMenuLoader {
|
||||
public:
|
||||
static std::vector<GridItem> load_external_items(app_location_t, NavigationView&);
|
||||
ExternalItemsMenuLoader() = delete;
|
||||
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
|
||||
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback);
|
||||
|
||||
private:
|
||||
static std::vector<DynamicBitmap<16, 16>> bitmaps;
|
||||
|
||||
static void run_external_app(ui::NavigationView&, std::filesystem::path);
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -111,6 +111,169 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
// When adding or removing apps from the Menu, please update it here:
|
||||
std::vector<AppInfoConsole> NavigationView::fixedAppListFC = {
|
||||
{"adsbrx", "ADS-B", RX},
|
||||
{"ais", "AIS Boats", RX},
|
||||
{"aprsrx", "APRS", RX},
|
||||
{"audio", "Audio", RX},
|
||||
{"blerx", "BLE Rx", RX},
|
||||
{"ert", "ERT Meter", RX},
|
||||
{"level", "Level", RX},
|
||||
{"pocsagrx", "POCSAG", RX},
|
||||
{"radiosnde", "Radiosnde", RX},
|
||||
{"recon", "Recon", RX},
|
||||
{"search", "Search", RX},
|
||||
{"tpms", "TPMS Cars", RX},
|
||||
{"weather", "Weather", RX},
|
||||
{"subghzd", "SubGhzD", RX},
|
||||
{"adsbtx", "ADS-B", TX},
|
||||
{"aprstx", "APRS TX", TX},
|
||||
{"bht", "BHT Xy/EP", TX},
|
||||
{"bletx", "BLE Tx", TX},
|
||||
{"morsetx", "Morse", TX},
|
||||
{"ooktx", "OOK", TX},
|
||||
{"pocsatx", "POCSAG TX", TX},
|
||||
{"rdstx", "RDS TX", TX},
|
||||
{"soundbrd", "Soundbrd", TX},
|
||||
{"sstvtx", "SSTV", TX},
|
||||
{"touchtune", "TouchTune", TX},
|
||||
{"capture", "Capture", RX},
|
||||
{"replay", "Replay", TX},
|
||||
{"remote", "Remote", TX},
|
||||
{"scanner", "Scanner", RX},
|
||||
{"microphone", "Microphone", TX},
|
||||
{"lookingglass", "Looking Glass", RX}};
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
home(false);
|
||||
if (strcmp(name, "adsbrx") == 0) {
|
||||
push<ADSBRxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ais") == 0) {
|
||||
push<AISAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "aprsrx") == 0) {
|
||||
push<APRSRXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "audio") == 0) {
|
||||
push<AnalogAudioView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "blerx") == 0) {
|
||||
push<BLERxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ert") == 0) {
|
||||
push<ERTAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "level") == 0) {
|
||||
push<LevelView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pocsagrx") == 0) {
|
||||
push<POCSAGAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "radiosnode") == 0) {
|
||||
push<SondeView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "recon") == 0) {
|
||||
push<ReconView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "search") == 0) {
|
||||
push<SearchView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "tpms") == 0) {
|
||||
push<TPMSAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "weather") == 0) {
|
||||
push<WeatherView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "subghzd") == 0) {
|
||||
push<SubGhzDView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "adsbtx") == 0) {
|
||||
push<ADSBTxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "aprstx") == 0) {
|
||||
push<APRSTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bht") == 0) {
|
||||
push<BHTView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bletx") == 0) {
|
||||
push<BLETxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "morsetx") == 0) {
|
||||
push<MorseView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ooktx") == 0) {
|
||||
push<EncodersView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pocsatx") == 0) {
|
||||
push<POCSAGTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "rdstx") == 0) {
|
||||
push<RDSView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "soundbrd") == 0) {
|
||||
push<SoundBoardView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sstvtx") == 0) {
|
||||
push<SSTVTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "touchtune") == 0) {
|
||||
push<TouchTunesView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "capture") == 0) {
|
||||
push<CaptureAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "replay") == 0) {
|
||||
push<PlaylistView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "remote") == 0) {
|
||||
push<RemoteView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "scanner") == 0) {
|
||||
push<ScannerView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "microphone") == 0) {
|
||||
push<MicTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "lookingglass") == 0) {
|
||||
push<GlassView>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* StatusTray ************************************************************/
|
||||
|
||||
StatusTray::StatusTray(Point pos)
|
||||
@@ -497,6 +660,14 @@ void NavigationView::pop(bool trigger_update) {
|
||||
if (on_pop) on_pop();
|
||||
}
|
||||
|
||||
void NavigationView::home(bool trigger_update) {
|
||||
while (view_stack.size() > 1) {
|
||||
pop(false);
|
||||
}
|
||||
|
||||
if (trigger_update) update_view();
|
||||
}
|
||||
|
||||
void NavigationView::display_modal(
|
||||
const std::string& title,
|
||||
const std::string& message) {
|
||||
@@ -751,6 +922,9 @@ SystemView::SystemView(
|
||||
Context& SystemView::context() const {
|
||||
return context_;
|
||||
}
|
||||
NavigationView* SystemView::get_navigation_view() {
|
||||
return &navigation_view;
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
switch (++overlay_active) {
|
||||
@@ -758,7 +932,7 @@ void SystemView::toggle_overlay() {
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_performance_counter = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
break;
|
||||
@@ -766,11 +940,12 @@ void SystemView::toggle_overlay() {
|
||||
this->remove_child(&this->overlay);
|
||||
this->add_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
shared_memory.request_m4_performance_counter = 2;
|
||||
break;
|
||||
case 3:
|
||||
this->remove_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
overlay_active = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
#include "diskio.h"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include "external_app.hpp"
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
@@ -57,6 +57,12 @@ enum modal_t {
|
||||
ABORT
|
||||
};
|
||||
|
||||
struct AppInfoConsole {
|
||||
const char* appCallName;
|
||||
const char* appFriendlyName;
|
||||
const app_location_t appLocation;
|
||||
};
|
||||
|
||||
class NavigationView : public View {
|
||||
public:
|
||||
std::function<void(const View&)> on_view_changed{};
|
||||
@@ -85,6 +91,7 @@ class NavigationView : public View {
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
void replace(View* v);
|
||||
void pop(bool trigger_update = true);
|
||||
void home(bool trigger_update);
|
||||
|
||||
void display_modal(const std::string& title, const std::string& message);
|
||||
void display_modal(
|
||||
@@ -99,6 +106,8 @@ class NavigationView : public View {
|
||||
* Returns true if the handler was bound successfully. */
|
||||
bool set_on_pop(std::function<void()> on_pop);
|
||||
|
||||
static std::vector<AppInfoConsole> fixedAppListFC; // fixed app list for console. vector, so can be incomplete and still iterateable
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in fixedAppListFC. This is to start apps from console
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
@@ -321,6 +330,8 @@ class SystemView : public View {
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
|
||||
NavigationView* get_navigation_view();
|
||||
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
|
||||
@@ -23,10 +23,20 @@
|
||||
#include "usb_serial_endpoints.h"
|
||||
#include "usb_serial_event.hpp"
|
||||
|
||||
uint32_t EVT_MASK_USB = EVENT_MASK(8);
|
||||
|
||||
extern void usb0_isr(void);
|
||||
|
||||
static Thread* thread_usb_event = NULL;
|
||||
|
||||
CH_IRQ_HANDLER(USB0_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
usb0_isr();
|
||||
|
||||
chSysLockFromIsr();
|
||||
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
@@ -46,6 +56,7 @@ uint32_t __strex(uint32_t val, volatile uint32_t* addr) {
|
||||
|
||||
void nvic_enable_irq(uint8_t irqn) {
|
||||
NVIC_ISER(irqn / 32) = (1 << (irqn % 32));
|
||||
thread_usb_event = chThdSelf();
|
||||
}
|
||||
|
||||
void usb_configuration_changed(usb_device_t* const device) {
|
||||
|
||||
@@ -264,7 +264,7 @@ uint8_t usb_descriptor_string_manufacturer[] = {
|
||||
};
|
||||
|
||||
uint8_t usb_descriptor_string_product[] = {
|
||||
43, // bLength
|
||||
34, // bLength
|
||||
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
|
||||
'P', 0x00,
|
||||
'o', 0x00,
|
||||
|
||||
@@ -40,13 +40,19 @@
|
||||
|
||||
SerialUSBDriver SUSBD1;
|
||||
|
||||
uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE];
|
||||
|
||||
void bulk_out_receive(void) {
|
||||
int ret;
|
||||
|
||||
while (chIQGetEmptyI(&SUSBD1.iqueue) < USB_BULK_BUFFER_SIZE)
|
||||
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
|
||||
|
||||
do {
|
||||
ret = usb_transfer_schedule(
|
||||
&usb_endpoint_bulk_out,
|
||||
&usb_endpoint_bulk_out.buffer[0],
|
||||
32,
|
||||
&usb_bulk_buffer[0],
|
||||
USB_BULK_BUFFER_SIZE,
|
||||
serial_bulk_transfer_complete,
|
||||
NULL);
|
||||
|
||||
@@ -57,25 +63,25 @@ void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferr
|
||||
(void)user_data;
|
||||
|
||||
chSysLockFromIsr();
|
||||
|
||||
for (unsigned int i = 0; i < bytes_transferred; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&SUSBD1.iqueue, usb_endpoint_bulk_out.buffer[i]);
|
||||
if (ret == Q_FULL)
|
||||
chThdSleepMilliseconds(1);
|
||||
ret = chIQPutI(&SUSBD1.iqueue, usb_bulk_buffer[i]);
|
||||
|
||||
if (ret == Q_FULL) {
|
||||
chDbgPanic("USB iqueue buffer full");
|
||||
}
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
static void onotify(GenericQueue* qp) {
|
||||
SerialUSBDriver* sdp = chQGetLink(qp);
|
||||
uint8_t buff[64];
|
||||
uint8_t buff[USBSERIAL_BUFFERS_SIZE];
|
||||
int n = chOQGetFullI(&sdp->oqueue);
|
||||
if (n > 64) n = 64; // don't overflow
|
||||
if (n > USBSERIAL_BUFFERS_SIZE) n = USBSERIAL_BUFFERS_SIZE; // don't overflow
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
buff[i] = chOQGetI(&sdp->oqueue);
|
||||
@@ -139,6 +145,45 @@ static const struct SerialUSBDriverVMT vmt = {
|
||||
|
||||
void init_serial_usb_driver(SerialUSBDriver* sdp) {
|
||||
sdp->vmt = &vmt;
|
||||
chIQInit(&sdp->iqueue, sdp->ib, SERIAL_BUFFERS_SIZE, NULL, sdp);
|
||||
chOQInit(&sdp->oqueue, sdp->ob, SERIAL_BUFFERS_SIZE, onotify, sdp);
|
||||
chIQInit(&sdp->iqueue, sdp->ib, USBSERIAL_BUFFERS_SIZE, NULL, sdp);
|
||||
chOQInit(&sdp->oqueue, sdp->ob, USBSERIAL_BUFFERS_SIZE, onotify, sdp);
|
||||
}
|
||||
|
||||
// queue handler from ch
|
||||
static msg_t qwait(GenericQueue* qp, systime_t time) {
|
||||
if (TIME_IMMEDIATE == time)
|
||||
return Q_TIMEOUT;
|
||||
currp->p_u.wtobjp = qp;
|
||||
queue_insert(currp, &qp->q_waiting);
|
||||
return chSchGoSleepTimeoutS(THD_STATE_WTQUEUE, time);
|
||||
}
|
||||
|
||||
// This function fills the output buffer, and sends all data in 1 packet
|
||||
size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n) {
|
||||
qnotify_t nfy = oqp->q_notify;
|
||||
size_t w = 0;
|
||||
|
||||
chDbgCheck(n > 0, "chOQWriteTimeout");
|
||||
chSysLock();
|
||||
while (TRUE) {
|
||||
while (chOQIsFullI(oqp)) {
|
||||
if (qwait((GenericQueue*)oqp, TIME_INFINITE) != Q_OK) {
|
||||
chSysUnlock();
|
||||
return w;
|
||||
}
|
||||
}
|
||||
while (!chOQIsFullI(oqp) && n > 0) {
|
||||
oqp->q_counter--;
|
||||
*oqp->q_wrptr++ = *bp++;
|
||||
if (oqp->q_wrptr >= oqp->q_top)
|
||||
oqp->q_wrptr = oqp->q_buffer;
|
||||
w++;
|
||||
--n;
|
||||
}
|
||||
if (nfy) nfy(oqp);
|
||||
|
||||
chSysUnlock(); /* Gives a preemption chance in a controlled point.*/
|
||||
if (n == 0) return w;
|
||||
chSysLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#define USB_BULK_BUFFER_SIZE 64
|
||||
|
||||
#ifndef USBSERIAL_BUFFERS_SIZE
|
||||
#define USBSERIAL_BUFFERS_SIZE 128
|
||||
#endif
|
||||
|
||||
struct SerialUSBDriverVMT {
|
||||
_base_asynchronous_channel_methods
|
||||
};
|
||||
@@ -31,10 +37,10 @@ struct SerialUSBDriverVMT {
|
||||
struct SerialUSBDriver {
|
||||
/** @brief Virtual Methods Table.*/
|
||||
const struct SerialUSBDriverVMT* vmt;
|
||||
InputQueue iqueue; /* Output queue.*/
|
||||
OutputQueue oqueue; /* Input circular buffer.*/
|
||||
uint8_t ib[SERIAL_BUFFERS_SIZE]; /* Output circular buffer.*/
|
||||
uint8_t ob[SERIAL_BUFFERS_SIZE];
|
||||
InputQueue iqueue; /* Output queue.*/
|
||||
OutputQueue oqueue; /* Input circular buffer.*/
|
||||
uint8_t ib[USBSERIAL_BUFFERS_SIZE]; /* Output circular buffer.*/
|
||||
uint8_t ob[USBSERIAL_BUFFERS_SIZE];
|
||||
};
|
||||
|
||||
typedef struct SerialUSBDriver SerialUSBDriver;
|
||||
@@ -44,3 +50,11 @@ extern SerialUSBDriver SUSBD1;
|
||||
void init_serial_usb_driver(SerialUSBDriver* sdp);
|
||||
void bulk_out_receive(void);
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -37,16 +37,17 @@
|
||||
#include "hackrf_cpld_data.hpp"
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "ff.h"
|
||||
#include "chprintf.h"
|
||||
#include "chqueues.h"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
#include "untar.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
|
||||
#define SHELL_WA_SIZE THD_WA_SIZE(1024 * 3)
|
||||
@@ -57,45 +58,6 @@ static EventDispatcher* getEventDispatcherInstance() {
|
||||
return _eventDispatcherInstance;
|
||||
}
|
||||
|
||||
// queue handler from ch
|
||||
static msg_t qwait(GenericQueue* qp, systime_t time) {
|
||||
if (TIME_IMMEDIATE == time)
|
||||
return Q_TIMEOUT;
|
||||
currp->p_u.wtobjp = qp;
|
||||
queue_insert(currp, &qp->q_waiting);
|
||||
return chSchGoSleepTimeoutS(THD_STATE_WTQUEUE, time);
|
||||
}
|
||||
|
||||
// This function fills the output buffer, and sends all data in 1 packet
|
||||
static size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n) {
|
||||
qnotify_t nfy = oqp->q_notify;
|
||||
size_t w = 0;
|
||||
|
||||
chDbgCheck(n > 0, "chOQWriteTimeout");
|
||||
chSysLock();
|
||||
while (TRUE) {
|
||||
while (chOQIsFullI(oqp)) {
|
||||
if (qwait((GenericQueue*)oqp, TIME_INFINITE) != Q_OK) {
|
||||
chSysUnlock();
|
||||
return w;
|
||||
}
|
||||
}
|
||||
while (!chOQIsFullI(oqp) && n > 0) {
|
||||
oqp->q_counter--;
|
||||
*oqp->q_wrptr++ = *bp++;
|
||||
if (oqp->q_wrptr >= oqp->q_top)
|
||||
oqp->q_wrptr = oqp->q_buffer;
|
||||
w++;
|
||||
--n;
|
||||
}
|
||||
if (nfy) nfy(oqp);
|
||||
|
||||
chSysUnlock(); /* Gives a preemption chance in a controlled point.*/
|
||||
if (n == 0) return w;
|
||||
chSysLock();
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_reboot(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)chp;
|
||||
(void)argc;
|
||||
@@ -158,11 +120,6 @@ static void cmd_sd_over_usb(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
m0_halt();
|
||||
}
|
||||
|
||||
std::filesystem::path path_from_string8(char* path) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
|
||||
return conv.from_bytes(path);
|
||||
}
|
||||
|
||||
bool strEndsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||
if (str.length() >= suffix.length()) {
|
||||
std::u16string endOfString = str.substr(str.length() - suffix.length());
|
||||
@@ -185,6 +142,13 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->display_modal("Flashing", "Flashing from serial.\rPlease wait!\nDevice will restart.");
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
@@ -196,6 +160,7 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
});
|
||||
if (res.empty()) {
|
||||
chprintf(chp, "error bad TAR file.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
path = res; // it will contain the last bin file in tar
|
||||
@@ -203,8 +168,10 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
// nothing to do for this case yet.
|
||||
} else {
|
||||
chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
|
||||
chprintf(chp, "Flashing: ");
|
||||
chprintf(chp, path.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
@@ -245,6 +212,9 @@ static void cmd_screenframe(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
evtd->enter_shell_working_mode();
|
||||
|
||||
for (int i = 0; i < ui::screen_height; i++) {
|
||||
std::array<ui::ColorRGB888, ui::screen_width> row;
|
||||
portapack::display.read_pixels({0, i, ui::screen_width, 1}, row);
|
||||
@@ -255,6 +225,9 @@ static void cmd_screenframe(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
chprintf(chp, "\r\n");
|
||||
}
|
||||
|
||||
evtd->exit_shell_working_mode();
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
@@ -272,21 +245,25 @@ static void cmd_screenframeshort(BaseSequentialStream* chp, int argc, char* argv
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
evtd->enter_shell_working_mode();
|
||||
|
||||
for (int y = 0; y < ui::screen_height; y++) {
|
||||
std::array<ui::ColorRGB888, ui::screen_width> row;
|
||||
portapack::display.read_pixels({0, y, ui::screen_width, 1}, row);
|
||||
for (int px = 0; px < ui::screen_width; px += 60) {
|
||||
char buffer[60];
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
buffer[i] = getChrFromRgb(row[px + i].r, row[px + i].g, row[px + i].b);
|
||||
}
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)buffer, 60);
|
||||
char buffer[242];
|
||||
for (int i = 0; i < 240; ++i) {
|
||||
buffer[i] = getChrFromRgb(row[i].r, row[i].g, row[i].b);
|
||||
}
|
||||
chprintf(chp, "\r\n");
|
||||
buffer[240] = '\r';
|
||||
buffer[241] = '\n';
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)buffer, 242);
|
||||
}
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
evtd->exit_shell_working_mode();
|
||||
chprintf(chp, "\r\nok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_write_memory(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
chprintf(chp, "usage: write_memory <address> <value (1 or 4 bytes)>\r\n");
|
||||
@@ -341,6 +318,11 @@ static void cmd_button(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
|
||||
control::debug::inject_switch(button);
|
||||
|
||||
// Wait two frame syncs to ensure action has painted
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
evtd->wait_finish_frame();
|
||||
evtd->wait_finish_frame();
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
@@ -363,6 +345,11 @@ static void cmd_touch(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::Start});
|
||||
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::End});
|
||||
|
||||
// Wait two frame syncs to ensure action has painted
|
||||
evtd->wait_finish_frame();
|
||||
evtd->wait_finish_frame();
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
@@ -401,185 +388,9 @@ static void cmd_keyboard(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
evtd->emulateKeyboard(chr);
|
||||
}
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: ls /\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path, "*")) {
|
||||
if (std::filesystem::is_directory(entry.status())) {
|
||||
chprintf(chp, "%s/\r\n", entry.path().string().c_str());
|
||||
} else if (std::filesystem::is_regular_file(entry.status())) {
|
||||
chprintf(chp, "%s\r\n", entry.path().string().c_str());
|
||||
} else {
|
||||
chprintf(chp, "%s *\r\n", entry.path().string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_sd_delete(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: rm <path>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
|
||||
if (!std::filesystem::file_exists(path)) {
|
||||
chprintf(chp, "file not found.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
delete_file(path);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
File* shell_file = nullptr;
|
||||
|
||||
static void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: filesize <path>\r\n");
|
||||
return;
|
||||
}
|
||||
auto path = path_from_string8(argv[0]);
|
||||
FILINFO res;
|
||||
auto stat = f_stat(path.tchar(), &res);
|
||||
if (stat == FR_OK) {
|
||||
chprintf(chp, "%lu\r\n", res.fsize);
|
||||
chprintf(chp, "ok\r\n");
|
||||
} else {
|
||||
chprintf(chp, "error\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: open <path>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file != nullptr) {
|
||||
chprintf(chp, "file already open\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
shell_file = new File();
|
||||
shell_file->open(path, false, true);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: seek <offset>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int address = (int)strtol(argv[0], NULL, 10);
|
||||
shell_file->seek(address);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if (argc != 0) {
|
||||
chprintf(chp, "usage: close\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
delete shell_file;
|
||||
shell_file = nullptr;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: read <number of bytes>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int size = (int)strtol(argv[0], NULL, 10);
|
||||
|
||||
uint8_t buffer[62];
|
||||
|
||||
do {
|
||||
File::Size bytes_to_read = size > 62 ? 62 : size;
|
||||
auto bytes_read = shell_file->read(buffer, bytes_to_read);
|
||||
if (bytes_read.is_error()) {
|
||||
chprintf(chp, "error %d\r\n", bytes_read.error());
|
||||
return;
|
||||
}
|
||||
std::string res = to_string_hex_array(buffer, bytes_read.value());
|
||||
res += "\r\n";
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
|
||||
if (bytes_to_read != bytes_read.value())
|
||||
return;
|
||||
|
||||
size -= bytes_to_read;
|
||||
} while (size > 0);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: write 0123456789ABCDEF\r\n";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t data_string_len = strlen(argv[0]);
|
||||
if (data_string_len % 2 != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < data_string_len; i++) {
|
||||
char c = argv[0][i];
|
||||
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[3] = {0, 0, 0};
|
||||
|
||||
for (size_t i = 0; i < data_string_len / 2; i++) {
|
||||
buffer[0] = argv[0][i * 2];
|
||||
buffer[1] = argv[0][i * 2 + 1];
|
||||
uint8_t value = (uint8_t)strtol(buffer, NULL, 16);
|
||||
shell_file->write(&value, 1);
|
||||
}
|
||||
// Wait two frame syncs to ensure action has painted
|
||||
evtd->wait_finish_frame();
|
||||
evtd->wait_finish_frame();
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
@@ -843,6 +654,78 @@ static void cmd_accessibility_readcurr(BaseSequentialStream* chp, int argc, char
|
||||
chprintf(chp, "\r\nok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_appstart(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "Usage: appstart APPCALLNAME");
|
||||
return;
|
||||
}
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
if (nav->StartAppByName(argv[0])) {
|
||||
chprintf(chp, "ok\r\n");
|
||||
return;
|
||||
}
|
||||
// since ext app loader changed, we can just pass the string to it, and it"ll return if started or not.
|
||||
std::string appwithpath = "/APPS/";
|
||||
appwithpath += argv[0];
|
||||
appwithpath += ".ppma";
|
||||
bool ret = ui::ExternalItemsMenuLoader::run_external_app(*nav, path_from_string8((char*)appwithpath.c_str()));
|
||||
if (!ret) {
|
||||
chprintf(chp, "error\r\n");
|
||||
return;
|
||||
}
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element) {
|
||||
if (strlen(element.appCallName) == 0) return;
|
||||
chprintf(chp, element.appCallName);
|
||||
chprintf(chp, " ");
|
||||
chprintf(chp, element.appFriendlyName);
|
||||
chprintf(chp, " ");
|
||||
switch (element.appLocation) {
|
||||
case RX:
|
||||
chprintf(chp, "[RX]\r\n");
|
||||
break;
|
||||
case TX:
|
||||
chprintf(chp, "[TX]\r\n");
|
||||
break;
|
||||
case UTILITIES:
|
||||
chprintf(chp, "[UTIL]\r\n");
|
||||
break;
|
||||
case DEBUG:
|
||||
chprintf(chp, "[DEBUG]\r\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// returns the installed apps, those can be called by appstart APPNAME
|
||||
static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
for (auto element : ui::NavigationView::fixedAppListFC) {
|
||||
printAppInfo(chp, element);
|
||||
}
|
||||
ui::ExternalItemsMenuLoader::load_all_external_items_callback([chp](ui::AppInfoConsole& info) {
|
||||
printAppInfo(chp, info);
|
||||
});
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage =
|
||||
"usage: cpld_read <device> <target>\r\n"
|
||||
@@ -1004,6 +887,35 @@ static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_gotgps(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: gotgps <lat> <lon> [altitude] [speed]\r\n";
|
||||
if (argc < 2 || argc > 4) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
float lat = atof(argv[0]);
|
||||
float lon = atof(argv[1]);
|
||||
int32_t altitude = 0;
|
||||
int32_t speed = 0;
|
||||
if (argc >= 3) altitude = strtol(argv[2], NULL, 10);
|
||||
if (argc >= 4) speed = strtol(argv[3], NULL, 10);
|
||||
GPSPosDataMessage msg{lat, lon, altitude, speed};
|
||||
EventDispatcher::send_message(msg);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_gotorientation(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: gotorientation <angle>\r\n";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
uint16_t angle = strtol(argv[0], NULL, 10);
|
||||
OrientationDataMessage msg{angle};
|
||||
EventDispatcher::send_message(msg);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -1018,20 +930,17 @@ static const ShellCommand commands[] = {
|
||||
{"button", cmd_button},
|
||||
{"touch", cmd_touch},
|
||||
{"keyboard", cmd_keyboard},
|
||||
{"ls", cmd_sd_list_dir},
|
||||
{"rm", cmd_sd_delete},
|
||||
{"open", cmd_sd_open},
|
||||
{"seek", cmd_sd_seek},
|
||||
{"close", cmd_sd_close},
|
||||
{"read", cmd_sd_read},
|
||||
{"write", cmd_sd_write},
|
||||
{"filesize", cmd_sd_filesize},
|
||||
USB_SERIAL_SHELL_SD_COMMANDS,
|
||||
{"rtcget", cmd_rtcget},
|
||||
{"rtcset", cmd_rtcset},
|
||||
{"cpld_info", cpld_info},
|
||||
{"cpld_read", cmd_cpld_read},
|
||||
{"accessibility_readall", cmd_accessibility_readall},
|
||||
{"accessibility_readcurr", cmd_accessibility_readcurr},
|
||||
{"applist", cmd_applist},
|
||||
{"appstart", cmd_appstart},
|
||||
{"gotgps", cmd_gotgps},
|
||||
{"gotorientation", cmd_gotorientation},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
@@ -1040,5 +949,5 @@ static const ShellConfig shell_cfg1 = {
|
||||
|
||||
void create_shell(EventDispatcher* evtd) {
|
||||
_eventDispatcherInstance = evtd;
|
||||
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO);
|
||||
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO + 10);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
#include "usb_serial_io.h"
|
||||
|
||||
#include "chprintf.h"
|
||||
#include "string_format.hpp"
|
||||
#include <cstring>
|
||||
|
||||
static File* shell_file = nullptr;
|
||||
|
||||
static bool report_on_error(BaseSequentialStream* chp, File::Error& error) {
|
||||
if (error.ok() == false) {
|
||||
chprintf(chp, "Error calling delete_file: %d %s\r\n", error.code(), error.what().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool report_on_error(BaseSequentialStream* chp, FRESULT error_code) {
|
||||
std::filesystem::filesystem_error error = error_code;
|
||||
return report_on_error(chp, error);
|
||||
}
|
||||
|
||||
static bool report_on_error(BaseSequentialStream* chp, Optional<File::Error>& error) {
|
||||
if (error.is_valid())
|
||||
return report_on_error(chp, error.value());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool report_on_error(BaseSequentialStream* chp, File::Result<uint64_t> error) {
|
||||
if (error.is_error()) {
|
||||
return report_on_error(chp, error.error());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: ls /\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path, "*")) {
|
||||
if (std::filesystem::is_directory(entry.status())) {
|
||||
chprintf(chp, "%s/\r\n", entry.path().string().c_str());
|
||||
} else if (std::filesystem::is_regular_file(entry.status())) {
|
||||
chprintf(chp, "%s\r\n", entry.path().string().c_str());
|
||||
} else {
|
||||
chprintf(chp, "%s *\r\n", entry.path().string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_sd_unlink(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: unlink <path>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
auto error = delete_file(path);
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_mkdir(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: mkdir <path>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
chprintf(chp, "directory already exists.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = make_new_directory(path);
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: filesize <path>\r\n");
|
||||
return;
|
||||
}
|
||||
auto path = path_from_string8(argv[0]);
|
||||
FILINFO res;
|
||||
auto stat = f_stat(path.tchar(), &res);
|
||||
if (report_on_error(chp, stat)) return;
|
||||
|
||||
chprintf(chp, "%lu\r\n", res.fsize);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: fopen <path>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file != nullptr) {
|
||||
chprintf(chp, "file already open\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
shell_file = new File();
|
||||
auto error = shell_file->open(path, false, true);
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: fseek <offset>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int address = (int)strtol(argv[0], NULL, 10);
|
||||
auto error = shell_file->seek(address);
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if (argc != 0) {
|
||||
chprintf(chp, "usage: fclose\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
delete shell_file;
|
||||
shell_file = nullptr;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_truncate(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if (argc != 0) {
|
||||
chprintf(chp, "usage: ftruncate\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = shell_file->truncate();
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_sync(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if (argc != 0) {
|
||||
chprintf(chp, "usage: fsync\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = shell_file->sync();
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_tell(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if (argc != 0) {
|
||||
chprintf(chp, "usage: ftell\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto current_position = shell_file->tell();
|
||||
|
||||
chprintf(chp, "%d\r\n", current_position);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: fread <number of bytes>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int size = (int)strtol(argv[0], NULL, 10);
|
||||
|
||||
uint8_t buffer[62];
|
||||
|
||||
do {
|
||||
File::Size bytes_to_read = size > 62 ? 62 : size;
|
||||
auto bytes_read = shell_file->read(buffer, bytes_to_read);
|
||||
if (report_on_error(chp, bytes_read)) return;
|
||||
|
||||
std::string res = to_string_hex_array(buffer, bytes_read.value());
|
||||
res += "\r\n";
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
|
||||
if (bytes_to_read != bytes_read.value())
|
||||
return;
|
||||
|
||||
size -= bytes_to_read;
|
||||
} while (size > 0);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_read_binary(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: frb <number of bytes>\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int size = (int)strtol(argv[0], NULL, 10);
|
||||
|
||||
uint8_t buffer[64];
|
||||
|
||||
do {
|
||||
File::Size bytes_to_read = size > 64 ? 64 : size;
|
||||
auto bytes_read = shell_file->read(buffer, bytes_to_read);
|
||||
if (report_on_error(chp, bytes_read)) return;
|
||||
|
||||
if (bytes_read.value() > 0)
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, buffer, bytes_read.value());
|
||||
|
||||
if (bytes_to_read != bytes_read.value())
|
||||
return;
|
||||
|
||||
size -= bytes_to_read;
|
||||
} while (size > 0);
|
||||
chprintf(chp, "\r\nok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: fwrite 0123456789ABCDEF\r\n";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t data_string_len = strlen(argv[0]);
|
||||
if (data_string_len % 2 != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < data_string_len; i++) {
|
||||
char c = argv[0][i];
|
||||
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[3] = {0, 0, 0};
|
||||
|
||||
for (size_t i = 0; i < data_string_len / 2; i++) {
|
||||
buffer[0] = argv[0][i * 2];
|
||||
buffer[1] = argv[0][i * 2 + 1];
|
||||
uint8_t value = (uint8_t)strtol(buffer, NULL, 16);
|
||||
auto error = shell_file->write(&value, 1);
|
||||
if (report_on_error(chp, error)) return;
|
||||
}
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
void cmd_sd_write_binary(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: fwb <number of bytes>\r\nfollowed by <number of bytes> of data";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_file == nullptr) {
|
||||
chprintf(chp, "no open file\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t size = (size_t)strtol(argv[0], NULL, 10);
|
||||
|
||||
chprintf(chp, "send %d bytes\r\n", size);
|
||||
|
||||
uint8_t buffer[USB_BULK_BUFFER_SIZE];
|
||||
|
||||
do {
|
||||
size_t bytes_to_read = size > USB_BULK_BUFFER_SIZE ? USB_BULK_BUFFER_SIZE : size;
|
||||
size_t bytes_read = chSequentialStreamRead(chp, &buffer[0], bytes_to_read);
|
||||
if (bytes_read != bytes_to_read)
|
||||
return;
|
||||
|
||||
auto error = shell_file->write(&buffer[0], bytes_read);
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
size -= bytes_read;
|
||||
} while (size > 0);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
#include "ff.h"
|
||||
#include "file.hpp"
|
||||
|
||||
void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_unlink(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_mkdir(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_truncate(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_sync(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_tell(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_read_binary(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
void cmd_sd_write_binary(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
|
||||
static std::filesystem::path path_from_string8(char* path) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
|
||||
return conv.from_bytes(path);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define USB_SERIAL_SHELL_SD_COMMANDS \
|
||||
{"ls", cmd_sd_list_dir}, \
|
||||
{"unlink", cmd_sd_unlink}, \
|
||||
{"mkdir", cmd_sd_mkdir}, \
|
||||
{"filesize", cmd_sd_filesize}, \
|
||||
{"fopen", cmd_sd_open}, \
|
||||
{"fseek", cmd_sd_seek}, \
|
||||
{"fclose", cmd_sd_close}, \
|
||||
{"ftruncate", cmd_sd_truncate}, \
|
||||
{"fsync", cmd_sd_sync}, \
|
||||
{"ftell", cmd_sd_tell}, \
|
||||
{"fread", cmd_sd_read}, \
|
||||
{"frb", cmd_sd_read_binary}, \
|
||||
{"fwrite", cmd_sd_write}, \
|
||||
{"fwb", cmd_sd_write_binary}
|
||||
// clang-format on
|
||||
@@ -123,10 +123,10 @@ constexpr gpdma::channel::Config config_rx() {
|
||||
/* TODO: Clean up terminology around "buffer", "transfer", "samples" */
|
||||
|
||||
constexpr size_t buffer_samples_log2n = 7;
|
||||
constexpr size_t buffer_samples = (1 << buffer_samples_log2n);
|
||||
constexpr size_t buffer_samples = (1 << buffer_samples_log2n); // 2^7 = 128 byte circular DMA buffer
|
||||
constexpr size_t transfers_per_buffer_log2n = 2;
|
||||
constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n);
|
||||
constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer;
|
||||
constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n); // 2^2 = 4 transfer buffers in the circular buffer
|
||||
constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer; // 128/4 = 32 samples in each transfer buffer
|
||||
constexpr size_t transfers_mask = transfers_per_buffer - 1;
|
||||
|
||||
constexpr size_t buffer_bytes = buffer_samples * sizeof(sample_t);
|
||||
@@ -144,6 +144,8 @@ static constexpr auto& gpdma_channel_i2s0_rx = gpdma::channels[portapack::i2s0_r
|
||||
static volatile const gpdma::channel::LLI* tx_next_lli = nullptr;
|
||||
static volatile const gpdma::channel::LLI* rx_next_lli = nullptr;
|
||||
|
||||
static bool single_tx_buffer = false;
|
||||
|
||||
static void tx_transfer_complete() {
|
||||
tx_next_lli = gpdma_channel_i2s0_tx.next_lli();
|
||||
}
|
||||
@@ -213,11 +215,16 @@ void disable() {
|
||||
gpdma_channel_i2s0_rx.disable();
|
||||
}
|
||||
|
||||
void shrink_tx_buffer() {
|
||||
single_tx_buffer = true;
|
||||
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[0]);
|
||||
}
|
||||
|
||||
buffer_t tx_empty_buffer() {
|
||||
const auto next_lli = tx_next_lli;
|
||||
if (next_lli) {
|
||||
const size_t next_index = next_lli - &lli_tx_loop[0];
|
||||
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
const size_t free_index = (single_tx_buffer) ? 0 : (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
return {reinterpret_cast<sample_t*>(lli_tx_loop[free_index].srcaddr), transfer_samples};
|
||||
} else {
|
||||
return {nullptr, 0};
|
||||
|
||||
@@ -47,6 +47,7 @@ void init();
|
||||
void configure();
|
||||
void enable();
|
||||
void disable();
|
||||
void shrink_tx_buffer();
|
||||
|
||||
audio::buffer_t tx_empty_buffer();
|
||||
audio::buffer_t rx_empty_buffer();
|
||||
|
||||
@@ -95,6 +95,20 @@ void BasebandThread::run() {
|
||||
buffer_c8_t buffer{
|
||||
buffer_tmp.p, buffer_tmp.count, sampling_rate_};
|
||||
|
||||
if (shared_memory.request_m4_performance_counter == 0x02) {
|
||||
uint8_t max = shared_memory.m4_performance_counter;
|
||||
for (size_t i = 0; i < buffer_tmp.count; i++) {
|
||||
int8_t a = buffer_tmp.p[i].real();
|
||||
if (a < 0)
|
||||
a = -a;
|
||||
|
||||
if (a > max)
|
||||
max = a;
|
||||
}
|
||||
|
||||
shared_memory.m4_performance_counter = max;
|
||||
}
|
||||
|
||||
if (baseband_processor_) {
|
||||
baseband_processor_->execute(buffer);
|
||||
}
|
||||
|
||||
@@ -135,13 +135,17 @@ void update_performance_counters() {
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
auto free_stack = (uint32_t)get_free_stack_space();
|
||||
auto free_heap = chCoreStatus();
|
||||
if (performance_counter_active == 0x01) {
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
auto free_stack = (uint32_t)get_free_stack_space();
|
||||
auto free_heap = chCoreStatus();
|
||||
|
||||
shared_memory.m4_cpu_usage = utilisation;
|
||||
shared_memory.m4_stack_usage = free_stack;
|
||||
shared_memory.m4_heap_usage = free_heap;
|
||||
shared_memory.m4_performance_counter = utilisation;
|
||||
shared_memory.m4_stack_usage = free_stack;
|
||||
shared_memory.m4_heap_usage = free_heap;
|
||||
} else if (performance_counter_active == 0x02) {
|
||||
shared_memory.m4_performance_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -41,6 +41,12 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output to speaker too
|
||||
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
|
||||
audio_data[imod32] = ((int16_t)audio_sample - 0x80) * (1.0f / 128.0f);
|
||||
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
|
||||
audio_output.write(audio_buffer);
|
||||
|
||||
sample = tone_gen.process(audio_sample - 0x80);
|
||||
|
||||
// FM
|
||||
@@ -95,6 +101,8 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
|
||||
tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight);
|
||||
progress_interval_samples = message.divider;
|
||||
resample_acc = 0;
|
||||
audio_output.configure(false);
|
||||
audio::dma::shrink_tx_buffer();
|
||||
}
|
||||
|
||||
void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
#include "baseband_thread.hpp"
|
||||
#include "tone_gen.hpp"
|
||||
#include "stream_output.hpp"
|
||||
#include "audio_output.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#define AUDIO_OUTPUT_BUFFER_SIZE 32
|
||||
|
||||
class AudioTXProcessor : public BasebandProcessor {
|
||||
public:
|
||||
@@ -48,6 +52,10 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
int32_t sample{0}, delta{};
|
||||
int8_t re{0}, im{0};
|
||||
|
||||
float audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
|
||||
buffer_f32_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
|
||||
AudioOutput audio_output{};
|
||||
|
||||
size_t progress_interval_samples = 0, progress_samples = 0;
|
||||
|
||||
bool configured{false};
|
||||
|
||||
@@ -186,7 +186,7 @@ uint8_t usb_descriptor_string_manufacturer[] = {
|
||||
|
||||
uint8_t usb_descriptor_string_product[] = {
|
||||
#ifdef HACKRF_ONE
|
||||
43, // bLength
|
||||
34, // bLength
|
||||
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
|
||||
'P', 0x00,
|
||||
'o', 0x00,
|
||||
|
||||
@@ -118,6 +118,8 @@ class Message {
|
||||
SubGhzFPRxConfigure = 60,
|
||||
WeatherData = 61,
|
||||
SubGhzDData = 62,
|
||||
GPSPosData = 63,
|
||||
OrientationData = 64,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -1301,4 +1303,33 @@ class SubGhzDDataMessage : public Message {
|
||||
uint64_t data = 0;
|
||||
};
|
||||
|
||||
class GPSPosDataMessage : public Message {
|
||||
public:
|
||||
constexpr GPSPosDataMessage(
|
||||
float lat = 200.0,
|
||||
float lon = 200.0,
|
||||
int32_t altitude = 0,
|
||||
int32_t speed = 0)
|
||||
: Message{ID::GPSPosData},
|
||||
lat{lat},
|
||||
lon{lon},
|
||||
altitude{altitude},
|
||||
speed{speed} {
|
||||
}
|
||||
float lat = 200.0;
|
||||
float lon = 200.0;
|
||||
int32_t altitude = 0;
|
||||
int32_t speed = 0;
|
||||
};
|
||||
|
||||
class OrientationDataMessage : public Message {
|
||||
public:
|
||||
constexpr OrientationDataMessage(
|
||||
uint16_t angle = 400)
|
||||
: Message{ID::OrientationData},
|
||||
angle{angle} {
|
||||
}
|
||||
uint16_t angle = 400; //>360 -> no orientation set
|
||||
};
|
||||
|
||||
#endif /*__MESSAGE_H__*/
|
||||
|
||||
@@ -71,7 +71,7 @@ struct SharedMemory {
|
||||
void set_baseband_ready() { baseband_ready = true; }
|
||||
|
||||
uint8_t volatile request_m4_performance_counter{0};
|
||||
uint8_t volatile m4_cpu_usage{0};
|
||||
uint8_t volatile m4_performance_counter{0};
|
||||
uint16_t volatile m4_stack_usage{0};
|
||||
uint32_t volatile m4_heap_usage{0};
|
||||
uint16_t volatile m4_buffer_missed{0};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://github.com/eried/portapack-mayhem/releases
|
||||
URL=https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://github.com/eried/portapack-mayhem/wiki/Update-firmware
|
||||
URL=https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
Plug HackRF+Portapack, set it in HackRF mode, launch flash_portapack_mayhem.bat
|
||||
|
||||
Read https://github.com/eried/portapack-mayhem/wiki/Update-firmware for more detailed information
|
||||
Read https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware for more detailed information
|
||||
@@ -15,7 +15,7 @@ REM Check if the firmware file exists
|
||||
if not exist portapack-h1_h2-mayhem.bin (
|
||||
echo The firmware file "portapack-h1_h2-mayhem.bin" does not exist.
|
||||
echo Please ensure that you have downloaded the latest release from:
|
||||
echo https://github.com/eried/portapack-mayhem/releases/
|
||||
echo https://github.com/portapack-mayhem/mayhem-firmware/releases/
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
@@ -23,6 +23,6 @@ if not exist portapack-h1_h2-mayhem.bin (
|
||||
|
||||
"utils/hackrf_update.exe" portapack-h1_h2-mayhem.bin
|
||||
echo.
|
||||
echo If your device never boot after flashing, please refer to https://github.com/eried/portapack-mayhem/wiki/Won't-boot
|
||||
echo If your device never boot after flashing, please refer to https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
|
||||
echo.
|
||||
pause
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://github.com/eried/portapack-havoc/wiki/GPS-Sim
|
||||
URL=https://github.com/portapack-mayhem/mayhem-firmware/wiki/GPS-Sim
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Sample files below can be used with Replay app.
|
||||
|
||||
See [Wiki](https://github.com/eried/portapack-mayhem/wiki/C16-format) for more info over C16 file format.
|
||||
See [Wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/C16-format) for more info over C16 file format.
|
||||
|
||||
| file | description | frequency |
|
||||
| ------ | ------ | ------: |
|
||||
|
||||
@@ -8,8 +8,8 @@ for the current splash screen to appear at power up, the "Show splash screen" op
|
||||
must also be enabled under Settings->UserInterface.
|
||||
|
||||
For more information on Splash Screens, see the following Wiki pages:
|
||||
https://github.com/eried/portapack-mayhem/wiki/Create-a-custom-splash-screen
|
||||
https://github.com/eried/portapack-mayhem/wiki/File-manager
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Create-a-custom-splash-screen
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/wiki/File-manager
|
||||
|
||||
Many additional Mayhem splash screens that users have shared online may be found in
|
||||
the Mayhem #splash-screens channel on Discord.
|
||||
|
||||
Reference in New Issue
Block a user