mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-21 15:09:03 +00:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c5275247e | |||
| a0185d28eb | |||
| 2500df310f | |||
| 54f9ff116b | |||
| 47f231ad63 | |||
| f90d3fabce | |||
| 4e276cdc71 | |||
| 0eb03373b1 | |||
| d5ea0f0369 | |||
| 22cc311447 | |||
| 4fbba205ad | |||
| 7b91103118 | |||
| c0aa4a1738 | |||
| e26f77ee77 | |||
| f53262783a | |||
| b456c18008 | |||
| fa4b74fd6f | |||
| 18bc2cf11c | |||
| 20f28c8331 | |||
| ea38a0fe48 | |||
| fb2e576b34 | |||
| 1d79e30d4b | |||
| be372e12bc | |||
| 00853f526a | |||
| 37ca7a601c | |||
| b50d18eafc | |||
| 1070d951e6 | |||
| a980fe21ac | |||
| 9e96715176 | |||
| fecfe8b8fc | |||
| dfdd52c667 | |||
| 695e6d19f4 | |||
| 6e9ecf8e7b | |||
| fd158e873a | |||
| f90bd44617 | |||
| 2602c417be | |||
| aa1264cf91 | |||
| c79abccffd | |||
| d3c9dc1f10 | |||
| 3c8335c942 | |||
| ac31f4781c | |||
| c80cae3638 | |||
| a1d7cf2b86 | |||
| 6f6d863a14 | |||
| 43a1bc0445 | |||
| 0d02f431c7 | |||
| 322b06d670 | |||
| db187301ab | |||
| 84cb32ee14 | |||
| 61bd696b69 | |||
| db65ae192a | |||
| 7553dafffc | |||
| cfdd1a4c22 | |||
| d4342e01a4 | |||
| 4342f5c0ee | |||
| bd781ce37b | |||
| c2e05dea48 | |||
| de3212cba5 | |||
| 9aff3f4121 | |||
| 6b6a00d511 | |||
| 806219f46e | |||
| a153cbf30b | |||
| 584af02dba | |||
| f344a1ca40 | |||
| 511675c9a1 | |||
| 7bbd608c90 | |||
| f941ae871a | |||
| eff214cc79 | |||
| 319ed3e7b4 | |||
| 288f6bd517 | |||
| 809abb6842 | |||
| 31082c63af | |||
| ff14008b43 | |||
| 1377516dce | |||
| 4162820409 | |||
| 4bbe1175c1 | |||
| ecd1a217d7 | |||
| 335cace137 | |||
| dbd708a1c7 | |||
| 4d842b5fae | |||
| e24f1b131d | |||
| 3f2c7351f1 | |||
| afc75f6cda | |||
| 69b17faddf | |||
| 2c118ae584 | |||
| 75c34747cb | |||
| 7754c0f37f | |||
| 37cc35d3c6 | |||
| 8a7aa9c0f8 | |||
| 22d6e34cd6 | |||
| 2229381ce3 | |||
| 4a63bdd7a0 |
@@ -2,6 +2,7 @@ name: Hardware / QC / QA / selling
|
||||
description: Issues that related with hardware, quality control, quality assurance, selling, etc.
|
||||
labels:
|
||||
- question
|
||||
- hardware problem
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -11,9 +12,6 @@ body:
|
||||
We would like to inform you that we can't provide much help regarding hardware/QC/QA/selling issues because we are not controlling various vendors/manufacturers, and also we are not profiting from you buying your device.
|
||||
|
||||
You are still welcome to ask, but please be aware that for this kind of issue, ask the seller would be the best option.
|
||||
|
||||
> [!IMPORTANT]
|
||||
If you are a seller/vendor and your customer has directed you to this page as a reference/evidence of a "broken device" or similar issue, please be aware that we are not providing any "official guarantee of a faulty device." We are only offering suggestions based on what the submitter has stated. We do not guarantee that the device is broken or anything else.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Hardware Issue Warning
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
add-comment:
|
||||
if: github.event.label.name == 'hardware problem'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Add warning comment
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
> [!IMPORTANT]
|
||||
If you are a seller/vendor and your customer has directed you to this page as a reference/evidence of a "broken device" or similar issue, please be aware that we are not providing any "official guarantee of a faulty device." We are only offering suggestions based on what the submitter has stated. We do not guarantee that the device is broken or anything else.
|
||||
@@ -1 +1 @@
|
||||
v2.0.2
|
||||
v2.1.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.1.0
|
||||
v2.2.0
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/issues/new/.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1 @@
|
||||
Please see [How-to-collaborate](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate)
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
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://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
|
||||
[<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
[<img src="https://camo.githubusercontent.com/c1f7dd1e7672324f60a513f0de23de76da6a669e63896a9de535d8c8093fc3c7/68747470733a2f2f7261772e6769746875622e636f6d2f7368617265627261696e65642f706f7274617061636b2d6861636b72662f6d61737465722f646f632f696d616765732f68617264776172652f706f7274617061636b5f68315f6f7065726174696e672e6a7067" height="310">]([https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h1r1r2))
|
||||
|
||||
# What is this?
|
||||
|
||||
@@ -26,7 +28,7 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/ABDO6H) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Please check [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/)
|
||||
@@ -191,7 +191,7 @@ set(CPPSRC
|
||||
clock_manager.cpp
|
||||
core_control.cpp
|
||||
database.cpp
|
||||
de_bruijn.cpp
|
||||
gradient.cpp
|
||||
rfm69.cpp
|
||||
event_m0.cpp
|
||||
file_reader.cpp
|
||||
@@ -294,7 +294,6 @@ set(CPPSRC
|
||||
apps/ui_flash_utility.cpp
|
||||
apps/ui_freqman.cpp
|
||||
apps/ui_iq_trim.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_looking_glass_app.cpp
|
||||
apps/ui_mictx.cpp
|
||||
apps/ui_modemsetup.cpp
|
||||
@@ -303,7 +302,6 @@ set(CPPSRC
|
||||
apps/ui_rds.cpp
|
||||
apps/ui_recon_settings.cpp
|
||||
apps/ui_recon.cpp
|
||||
# apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
apps/ui_search.cpp
|
||||
apps/ui_settings.cpp
|
||||
|
||||
@@ -188,6 +188,7 @@ void copy_to_radio_model(const AppSettings& settings) {
|
||||
settings.am_config_index,
|
||||
settings.nbfm_config_index,
|
||||
settings.wfm_config_index,
|
||||
settings.wfmam_config_index,
|
||||
settings.squelch);
|
||||
}
|
||||
|
||||
@@ -218,6 +219,7 @@ void copy_from_radio_model(AppSettings& settings) {
|
||||
settings.am_config_index = receiver_model.am_configuration();
|
||||
settings.nbfm_config_index = receiver_model.nbfm_configuration();
|
||||
settings.wfm_config_index = receiver_model.wfm_configuration();
|
||||
settings.wfmam_config_index = receiver_model.wfmam_configuration();
|
||||
}
|
||||
|
||||
settings.step = receiver_model.frequency_step();
|
||||
@@ -271,6 +273,7 @@ SettingsManager::SettingsManager(
|
||||
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
|
||||
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
|
||||
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
|
||||
bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
|
||||
bindings_.emplace_back("squelch"sv, &settings_.squelch);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ struct AppSettings {
|
||||
uint8_t am_config_index = 0;
|
||||
uint8_t nbfm_config_index = 0;
|
||||
uint8_t wfm_config_index = 0;
|
||||
uint8_t wfmam_config_index = 0;
|
||||
uint8_t squelch = 80;
|
||||
uint8_t volume;
|
||||
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
|
||||
|
||||
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||
ais::format::text(entry_.name),
|
||||
0,
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::NONE,
|
||||
GeoPos::spd_unit::KNOTS,
|
||||
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
||||
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
||||
entry_.last_position.true_heading,
|
||||
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
|
||||
|
||||
void AISRecentEntryDetailView::update_position() {
|
||||
if (send_updates)
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
|
||||
}
|
||||
|
||||
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
|
||||
if (geomap_view && send_updates) {
|
||||
GeoMarker marker{};
|
||||
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
|
||||
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
|
||||
marker.angle = entry.last_position.true_heading;
|
||||
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
|
||||
auto markerStored = geomap_view->store_marker(marker);
|
||||
return markerStored == MARKER_STORED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
|
||||
if (geomap_view && send_updates) {
|
||||
geomap_view->clear_markers();
|
||||
for (const auto& entry : entries) {
|
||||
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
|
||||
add_map_marker(entry);
|
||||
// }
|
||||
}
|
||||
update_position(); // to update view
|
||||
}
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
@@ -415,14 +439,27 @@ AISAppView::AISAppView(NavigationView& nav)
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
AISAppView::~AISAppView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AISAppView::on_tick_second() {
|
||||
++timer_seconds;
|
||||
if (timer_seconds % 10 == 0) {
|
||||
if (recent_entry_detail_view.hidden()) return;
|
||||
recent_entry_detail_view.update_map_markers(recent);
|
||||
}
|
||||
}
|
||||
|
||||
void AISAppView::focus() {
|
||||
options_channel.focus();
|
||||
}
|
||||
@@ -461,6 +498,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
|
||||
recent_entry_detail_view.hidden(false);
|
||||
recent_entry_detail_view.set_entry(entry);
|
||||
recent_entry_detail_view.focus();
|
||||
recent_entry_detail_view.update_map_markers(recent);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -125,10 +125,14 @@ class AISRecentEntryDetailView : public View {
|
||||
void update_position();
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
bool add_map_marker(const AISRecentEntry& entry);
|
||||
void update_map_markers(AISRecentEntries& entries);
|
||||
|
||||
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
|
||||
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
|
||||
|
||||
GeoMapView* get_geomap_view() { return geomap_view; }
|
||||
|
||||
private:
|
||||
AISRecentEntry entry_{};
|
||||
|
||||
@@ -210,11 +214,13 @@ class AISAppView : public View {
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
SignalToken signal_token_tick_second{};
|
||||
uint8_t timer_seconds = 0;
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AISPacket,
|
||||
@@ -229,6 +235,7 @@ class AISAppView : public View {
|
||||
void on_packet(const ais::Packet& packet);
|
||||
void on_show_list();
|
||||
void on_show_detail(const AISRecentEntry& entry);
|
||||
void on_tick_second();
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -52,20 +52,20 @@ AMOptionsView::AMOptionsView(
|
||||
&zoom_config,
|
||||
});
|
||||
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(previous_filter_array_index + n);
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) { // n , has two option values. when GUI =zoom+1 => (0), when GUI=zoom+2 (6)
|
||||
receiver_model.set_am_configuration(view->get_previous_AM_mode_option() + n); // n (0 or 6)
|
||||
view->set_zoom_factor(AM_MODULATION, n);
|
||||
view->set_previous_zoom_option(n);
|
||||
};
|
||||
|
||||
// restore zoom selection
|
||||
zoom_config.set_by_value(view->get_zoom_factor(AM_MODULATION));
|
||||
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_by_value(receiver_model.am_configuration());
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // freqman.cpp to the options_config, only allowing 5 modes freqman_bandwidths[AM] {"DSB 9k", 0}, {"DSB 6k", 1}, {"USB+3k", 2}, {"LSB-3k", 3}, {"CW", 4},
|
||||
options_config.set_by_value(receiver_model.am_configuration() - view->get_previous_zoom_option()); // restore AM GUI option mode , AM FIR index filters (0..11) values , <baseband::AMConfig, 12> am_configs has 12 fir index elements.
|
||||
options_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(n);
|
||||
previous_filter_array_index = n;
|
||||
zoom_config.set_by_value(view->get_zoom_factor(AM_MODULATION));
|
||||
receiver_model.set_am_configuration(n + view->get_previous_zoom_option()); // we select proper FIR AM filter (0..11), = 0..4 GUI AM modes + offset +6 (if zoom+2)
|
||||
view->set_previous_AM_mode_option(n); // (0..4) allowing 5 AM modes (DSB9K, DSB6K, USB,LSB, CW)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,6 +114,26 @@ WFMOptionsView::WFMOptionsView(
|
||||
};
|
||||
}
|
||||
|
||||
/* WFMAMAptOptionsView *******************************************************/
|
||||
|
||||
WFMAMAptOptionsView::WFMAMAptOptionsView(
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_by_value(receiver_model.wfmam_configuration());
|
||||
options_config.on_change = [this](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_wfmam_configuration(n);
|
||||
};
|
||||
}
|
||||
|
||||
/* AMFMAptOptionsView *********************************************************/
|
||||
|
||||
AMFMAptOptionsView::AMFMAptOptionsView(
|
||||
@@ -282,6 +302,22 @@ void AnalogAudioView::set_zoom_factor(uint8_t mode, uint8_t zoom) { // define a
|
||||
zoom_factor_amfm = zoom;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_previous_AM_mode_option() {
|
||||
return previous_AM_mode_option;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_previous_AM_mode_option(uint8_t mode) {
|
||||
previous_AM_mode_option = mode;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_previous_zoom_option() {
|
||||
return previous_zoom;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_previous_zoom_option(uint8_t zoom) {
|
||||
previous_zoom = zoom;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read iq_phase_calibration_value
|
||||
return iq_phase_calibration_value;
|
||||
}
|
||||
@@ -399,6 +435,12 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
widget = std::make_unique<WFMAMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(true);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
widget = std::make_unique<AMFMAptOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
@@ -446,7 +488,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it.
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_am_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
@@ -485,7 +530,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
sampling_rate = 48000;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -40,7 +40,6 @@ class AnalogAudioView;
|
||||
class AMOptionsView : public View {
|
||||
public:
|
||||
AMOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
int16_t previous_filter_array_index = 0;
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
@@ -59,7 +58,7 @@ class AMOptionsView : public View {
|
||||
{23 * 8, 0 * 16},
|
||||
7,
|
||||
{{"ZOOM x1", 0},
|
||||
{"ZOOM x2", 6}} // offset index array filters.
|
||||
{"ZOOM x2", 6}} // offset index AM modes array FIR filters.
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,7 +74,7 @@ class AMFMAptOptionsView : public View {
|
||||
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
6, // Max option length chars
|
||||
17, // Max option length chars "USB+FM(Wefax Apt)"
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
|
||||
}};
|
||||
@@ -133,6 +132,23 @@ class WFMOptionsView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
class WFMAMAptOptionsView : public View {
|
||||
public:
|
||||
WFMAMAptOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
16, // Max option char length "80k-NOAA Apt LPF" , example.
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
public:
|
||||
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
@@ -169,7 +185,7 @@ class SPECOptionsView : public View {
|
||||
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
|
||||
"Rx_IQ_CAL "};
|
||||
NumberField field_rx_iq_phase_cal{
|
||||
{28 * 8, 0 * 16},
|
||||
{screen_width - 2 * 8, 0 * 16},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
@@ -200,6 +216,12 @@ class AnalogAudioView : public View {
|
||||
uint8_t get_zoom_factor(uint8_t mode);
|
||||
void set_zoom_factor(uint8_t mode, uint8_t zoom);
|
||||
|
||||
uint8_t get_previous_AM_mode_option();
|
||||
void set_previous_AM_mode_option(uint8_t mode);
|
||||
|
||||
uint8_t get_previous_zoom_option();
|
||||
void set_previous_zoom_option(uint8_t zoom);
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
@@ -208,6 +230,9 @@ class AnalogAudioView : public View {
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
uint8_t zoom_factor_am{0}; // initial zoom factor in AM mode
|
||||
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
|
||||
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio",
|
||||
app_settings::Mode::RX,
|
||||
@@ -215,9 +240,11 @@ class AnalogAudioView : public View {
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||
{"zoom_factor_am"sv, &zoom_factor_am}, // we are saving and restoring AM ZOOM factor from Settings.
|
||||
{"zoom_factor_amfm"sv, &zoom_factor_amfm}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
{"previous_AM_mode_option"sv, &previous_AM_mode_option}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
}};
|
||||
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
|
||||
size_t spec_bw_index = 0;
|
||||
@@ -251,11 +278,12 @@ class AnalogAudioView : public View {
|
||||
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
|
||||
}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
Text text_ctcss{
|
||||
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
|
||||
@@ -264,7 +292,7 @@ class AnalogAudioView : public View {
|
||||
std::unique_ptr<Widget> options_widget{};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
{0 * 8, 2 * 16, screen_width, 1 * 16},
|
||||
u"AUD",
|
||||
u"AUDIO",
|
||||
RecordView::FileType::WAV,
|
||||
|
||||
@@ -170,7 +170,7 @@ class BLECommView : public View {
|
||||
"-"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
{0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
|
||||
|
||||
std::string str_log{""};
|
||||
bool logging{false};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -21,7 +22,6 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ble_rx_app.hpp"
|
||||
#include "ble_rx_app.hpp"
|
||||
#include "ui_modemsetup.hpp"
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
@@ -67,6 +68,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
|
||||
static bool db_checked = false;
|
||||
static bool db_exists = false;
|
||||
|
||||
if (!db_checked) {
|
||||
database db;
|
||||
database::MacAddressDBRecord dummy_record;
|
||||
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
|
||||
db_exists = (test_result != DATABASE_NOT_FOUND);
|
||||
db_checked = true;
|
||||
}
|
||||
|
||||
if (!db_exists) {
|
||||
vendor_name = "macaddress.db not found";
|
||||
return MAC_DB_NOT_FOUND;
|
||||
}
|
||||
|
||||
database db;
|
||||
database::MacAddressDBRecord record;
|
||||
|
||||
// Convert MAC address to hex string
|
||||
std::string mac_hex = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Only need first 3 bytes for OUI
|
||||
mac_hex += to_string_hex(mac_address[i], 2);
|
||||
}
|
||||
|
||||
int result = db.retrieve_macaddress_record(&record, mac_hex);
|
||||
|
||||
if (result == DATABASE_RECORD_FOUND) {
|
||||
vendor_name = std::string(record.vendor_name);
|
||||
return MAC_VENDOR_FOUND;
|
||||
} else {
|
||||
vendor_name = "Unknown";
|
||||
return MAC_VENDOR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
std::string lookup_mac_vendor(const uint8_t* mac_address) {
|
||||
std::string vendor_name;
|
||||
lookup_mac_vendor_status(mac_address, vendor_name);
|
||||
return vendor_name;
|
||||
}
|
||||
|
||||
void reverse_byte_array(uint8_t* arr, int length) {
|
||||
int start = 0;
|
||||
int end = length - 1;
|
||||
@@ -150,8 +195,15 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
|
||||
}
|
||||
|
||||
std::string hitsStr;
|
||||
|
||||
if (!entry.informationString.empty()) {
|
||||
hitsStr = entry.informationString;
|
||||
} else {
|
||||
hitsStr = to_string_dec_int(entry.numHits);
|
||||
}
|
||||
|
||||
// Pushing single digit values down right justified.
|
||||
std::string hitsStr = to_string_dec_int(entry.numHits);
|
||||
int hitsDigits = hitsStr.length();
|
||||
uint8_t hits_spacing = 8 - hitsDigits;
|
||||
|
||||
@@ -165,7 +217,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line += pad_string_with_spaces(db_spacing) + dbStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
|
||||
|
||||
painter.draw_string(target_rect.location(), row_style, line);
|
||||
}
|
||||
|
||||
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
|
||||
@@ -178,10 +233,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
&text_mac_address,
|
||||
&label_pdu_type,
|
||||
&text_pdu_type,
|
||||
&label_vendor,
|
||||
&text_vendor,
|
||||
&labels});
|
||||
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
nav.pop();
|
||||
@@ -203,6 +262,7 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
nav,
|
||||
packetFileBuffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this, packetToSave](std::string& buffer) {
|
||||
on_save_file(buffer, packetToSave);
|
||||
});
|
||||
@@ -369,6 +429,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
|
||||
|
||||
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -446,6 +510,8 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
&button_switch,
|
||||
&recent_entries_view});
|
||||
|
||||
async_tx_states_when_entered = portapack::async_tx_enabled;
|
||||
|
||||
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
|
||||
nav_.push<BleRecentEntryDetailView>(entry);
|
||||
};
|
||||
@@ -453,9 +519,9 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
check_serial_log.on_select = [this](Checkbox&, bool v) {
|
||||
serial_logging = v;
|
||||
if (v) {
|
||||
usb_serial_thread = std::make_unique<UsbSerialThread>();
|
||||
portapack::async_tx_enabled = true;
|
||||
} else {
|
||||
usb_serial_thread.reset();
|
||||
portapack::async_tx_enabled = false;
|
||||
}
|
||||
};
|
||||
check_serial_log.set_value(serial_logging);
|
||||
@@ -471,6 +537,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
nav_,
|
||||
filterBuffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
on_filter_change(buffer);
|
||||
});
|
||||
@@ -479,7 +546,6 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
str_log = "";
|
||||
logging = v;
|
||||
|
||||
if (logger && logging)
|
||||
@@ -493,6 +559,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
nav,
|
||||
listFileBuffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
on_save_file(buffer);
|
||||
});
|
||||
@@ -500,6 +567,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
|
||||
button_clear_list.on_select = [this](Button&) {
|
||||
recent.clear();
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
button_switch.on_select = [&nav](Button&) {
|
||||
@@ -541,7 +609,10 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
|
||||
options_filter.on_change = [this](size_t index, int32_t v) {
|
||||
filter_index = (uint8_t)index;
|
||||
recent.clear();
|
||||
handle_filter_options(v);
|
||||
uniqueParsing = filter_index == 2 ? true : false;
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(channel_index, true);
|
||||
@@ -714,10 +785,61 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
if (!logging) {
|
||||
str_log = "";
|
||||
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
|
||||
|
||||
// Start of Packet stuffing.
|
||||
// Masking off the top 2 bytes to avoid invalid keys.
|
||||
|
||||
uint64_t key = macAddressEncoded & 0xFFFFFFFFFFFF;
|
||||
bool packetExists = false;
|
||||
|
||||
// If found store into tempEntry to modify.
|
||||
auto it = find(recent, key);
|
||||
if (it != recent.end()) {
|
||||
recent.push_front(*it);
|
||||
recent.erase(it);
|
||||
updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
|
||||
packetExists = true;
|
||||
} else {
|
||||
recent.emplace_front(key);
|
||||
truncate_entries(recent);
|
||||
|
||||
packetExists = updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
|
||||
|
||||
// If parsing failed, remove entry.
|
||||
if (!packetExists) {
|
||||
recent.erase(recent.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (packetExists) {
|
||||
handle_filter_options(options_filter.selected_index());
|
||||
handle_entries_sort(options_sort.selected_index());
|
||||
|
||||
if (!searchList.empty()) {
|
||||
auto it = searchList.begin();
|
||||
|
||||
while (it != searchList.end()) {
|
||||
std::string searchStr = (std::string)*it;
|
||||
|
||||
if (recent.front().dataString.find(searchStr) != std::string::npos) {
|
||||
searchList.erase(it);
|
||||
found_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
|
||||
}
|
||||
}
|
||||
|
||||
log_ble_packet(packet);
|
||||
}
|
||||
|
||||
void BLERxView::log_ble_packet(BlePacketData* packet) {
|
||||
str_console = "";
|
||||
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
|
||||
str_console += " Len:";
|
||||
str_console += to_string_dec_uint(packet->size);
|
||||
@@ -731,49 +853,13 @@ void BLERxView::on_data(BlePacketData* packet) {
|
||||
str_console += to_string_hex(packet->data[i], 2);
|
||||
}
|
||||
|
||||
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
|
||||
|
||||
// Start of Packet stuffing.
|
||||
// Masking off the top 2 bytes to avoid invalid keys.
|
||||
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
|
||||
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
|
||||
|
||||
// Add entries if they meet the criteria.
|
||||
// auto value = filter;
|
||||
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
||||
// });
|
||||
handle_filter_options(options_filter.selected_index());
|
||||
|
||||
handle_entries_sort(options_sort.selected_index());
|
||||
|
||||
// Log at End of Packet.
|
||||
if (logger && logging) {
|
||||
logger->log_raw_data(str_console + "\r\n");
|
||||
logger->log_raw_data(str_console);
|
||||
}
|
||||
|
||||
if (serial_logging) {
|
||||
usb_serial_thread->serial_str = str_console + "\r\n";
|
||||
usb_serial_thread->str_ready = true;
|
||||
}
|
||||
str_console = "";
|
||||
|
||||
if (!searchList.empty()) {
|
||||
auto it = searchList.begin();
|
||||
|
||||
while (it != searchList.end()) {
|
||||
std::string searchStr = (std::string)*it;
|
||||
|
||||
if (entry.dataString.find(searchStr) != std::string::npos) {
|
||||
searchList.erase(it);
|
||||
found_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
|
||||
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,13 +923,10 @@ void BLERxView::on_timer() {
|
||||
timer_count = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
baseband::set_btlerx(channel_number);
|
||||
|
||||
int randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
baseband::set_btlerx(randomChannel);
|
||||
channel_number = (channel_number < 39) ? channel_number + 1 : 37;
|
||||
}
|
||||
}
|
||||
if (ble_rx_error != BLE_RX_NO_ERROR) {
|
||||
@@ -914,20 +997,23 @@ void BLERxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
}
|
||||
|
||||
BLERxView::~BLERxView() {
|
||||
portapack::async_tx_enabled = async_tx_states_when_entered;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
|
||||
bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
|
||||
std::string data_string;
|
||||
|
||||
bool success = false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < packet->dataLen; i++) {
|
||||
data_string += to_string_hex(packet->data[i], 2);
|
||||
}
|
||||
|
||||
entry.dbValue = packet->max_dB;
|
||||
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
|
||||
entry.timestamp = to_string_timestamp(rtc_time::now());
|
||||
entry.dataString = data_string;
|
||||
|
||||
@@ -943,9 +1029,14 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.packetData.macAddress[4] = packet->macAddress[4];
|
||||
entry.packetData.macAddress[5] = packet->macAddress[5];
|
||||
|
||||
entry.numHits++;
|
||||
entry.pduType = pdu_type;
|
||||
entry.channelNumber = channel_number;
|
||||
entry.numHits++;
|
||||
|
||||
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
|
||||
std::string vendor_name;
|
||||
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
|
||||
}
|
||||
|
||||
// Parse Data Section into buffer to be interpretted later.
|
||||
for (int i = 0; i < packet->dataLen; i++) {
|
||||
@@ -955,35 +1046,55 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.include_name = check_name.value();
|
||||
|
||||
// Only parse name for advertisment packets and empty name entries
|
||||
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
|
||||
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
|
||||
|
||||
uint8_t currentByte = 0;
|
||||
uint8_t length = 0;
|
||||
uint8_t type = 0;
|
||||
|
||||
std::string decoded_data;
|
||||
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
|
||||
length = advertiseData->Data[currentByte++];
|
||||
type = advertiseData->Data[currentByte++];
|
||||
|
||||
// Subtract 1 because type is part of the length.
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
|
||||
if (type == 0x08 || type == 0x09) {
|
||||
decoded_data += (char)advertiseData->Data[currentByte];
|
||||
}
|
||||
currentByte++;
|
||||
}
|
||||
if (!decoded_data.empty()) {
|
||||
entry.nameString = std::move(decoded_data);
|
||||
break;
|
||||
}
|
||||
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
|
||||
if (uniqueParsing) {
|
||||
// Add your unique beacon parsing function here.
|
||||
}
|
||||
|
||||
if (!success && !uniqueParsing) {
|
||||
success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
|
||||
}
|
||||
|
||||
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
|
||||
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
|
||||
reverse_byte_array(directed_mac_data->A1, 6);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
|
||||
uint8_t currentByte, currentLength, currentType = 0;
|
||||
std::string tempName = "";
|
||||
|
||||
for (currentByte = 0; currentByte < length;) {
|
||||
currentLength = data[currentByte++];
|
||||
currentType = data[currentByte++];
|
||||
|
||||
// Subtract 1 because type is part of the length.
|
||||
for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
|
||||
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
|
||||
if (currentType == 0x08 || currentType == 0x09) {
|
||||
tempName += (char)data[currentByte];
|
||||
}
|
||||
currentByte++;
|
||||
}
|
||||
|
||||
if (!tempName.empty()) {
|
||||
nameString = tempName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
informationString = "";
|
||||
|
||||
if (!informationString.empty()) {
|
||||
// Option to change title of Hits Column.
|
||||
// Setting to default for now.
|
||||
columns.set(1, "Hits", 7);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "database.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "usb_serial_thread.hpp"
|
||||
@@ -72,10 +74,17 @@ typedef enum {
|
||||
RESERVED8 = 15
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
typedef enum {
|
||||
MAC_VENDOR_UNKNOWN = 0,
|
||||
MAC_VENDOR_FOUND = 1,
|
||||
MAC_VENDOR_NOT_FOUND = 2,
|
||||
MAC_DB_NOT_FOUND = 3
|
||||
} MAC_VENDOR_STATUS;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffff;
|
||||
static constexpr Key invalid_key = 0xFFFFFFFFFFFF;
|
||||
|
||||
uint64_t macAddress;
|
||||
int dbValue;
|
||||
@@ -83,10 +92,12 @@ struct BleRecentEntry {
|
||||
std::string timestamp;
|
||||
std::string dataString;
|
||||
std::string nameString;
|
||||
std::string informationString;
|
||||
bool include_name;
|
||||
uint16_t numHits;
|
||||
ADV_PDU_TYPE pduType;
|
||||
uint8_t channelNumber;
|
||||
MAC_VENDOR_STATUS vendor_status;
|
||||
bool entryFound;
|
||||
|
||||
BleRecentEntry()
|
||||
@@ -101,10 +112,12 @@ struct BleRecentEntry {
|
||||
timestamp{},
|
||||
dataString{},
|
||||
nameString{},
|
||||
informationString{},
|
||||
include_name{},
|
||||
numHits{},
|
||||
pduType{},
|
||||
channelNumber{},
|
||||
vendor_status{MAC_VENDOR_UNKNOWN},
|
||||
entryFound{} {
|
||||
}
|
||||
|
||||
@@ -152,6 +165,13 @@ class BleRecentEntryDetailView : public View {
|
||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_vendor{
|
||||
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_vendor{
|
||||
{7 * 8, 2 * 16, 23 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
@@ -198,15 +218,18 @@ class BLERxView : public View {
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
|
||||
void on_data(BlePacketData* packetData);
|
||||
void log_ble_packet(BlePacketData* packet);
|
||||
void on_filter_change(std::string value);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void file_error();
|
||||
void on_timer();
|
||||
void handle_entries_sort(uint8_t index);
|
||||
void handle_filter_options(uint8_t index);
|
||||
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState radio_state_{
|
||||
2402000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
@@ -216,9 +239,11 @@ class BLERxView : public View {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t sort_index{0};
|
||||
uint8_t filter_index{0};
|
||||
bool uniqueParsing = false;
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool serial_logging{false};
|
||||
bool async_tx_states_when_entered{false};
|
||||
|
||||
bool name_enable{true};
|
||||
app_settings::SettingsManager settings_{
|
||||
@@ -242,7 +267,7 @@ class BLERxView : public View {
|
||||
bool auto_channel = false;
|
||||
|
||||
int16_t timer_count{0};
|
||||
int16_t timer_period{6}; // 100ms
|
||||
int16_t timer_period{2}; // 25ms
|
||||
|
||||
std::string filterBuffer{};
|
||||
std::string listFileBuffer{};
|
||||
@@ -306,9 +331,10 @@ class BLERxView : public View {
|
||||
|
||||
OptionsField options_filter{
|
||||
{18 * 8 + 2, 2 * 8},
|
||||
4,
|
||||
7,
|
||||
{{"Data", 0},
|
||||
{"MAC", 1}}};
|
||||
{"MAC", 1},
|
||||
{"Unique", 2}}};
|
||||
|
||||
Checkbox check_log{
|
||||
{10 * 8, 4 * 8 + 2},
|
||||
@@ -340,18 +366,18 @@ class BLERxView : public View {
|
||||
true};
|
||||
|
||||
// Console console{
|
||||
// {0, 10 * 8, 240, 240}};
|
||||
// {0, 10 * 8, screen_height, screen_height-80}};
|
||||
|
||||
Button button_clear_list{
|
||||
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
|
||||
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
|
||||
"Clear"};
|
||||
|
||||
Button button_save_list{
|
||||
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
|
||||
{11 * 8, screen_height - (16 + 32), 11 * 8, 32},
|
||||
"Export CSV"};
|
||||
|
||||
Button button_switch{
|
||||
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
|
||||
{screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
|
||||
"Tx"};
|
||||
|
||||
std::string str_log{""};
|
||||
@@ -360,10 +386,10 @@ class BLERxView : public View {
|
||||
BleRecentEntries recent{};
|
||||
BleRecentEntries tempList{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Mac Address", 17},
|
||||
RecentEntriesColumns columns{{
|
||||
{"Name", 17},
|
||||
{"Hits", 7},
|
||||
{"dB", 4},
|
||||
{"dBm", 4},
|
||||
}};
|
||||
|
||||
BleRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
@@ -319,7 +319,8 @@ void BLETxView::on_tx_progress(const bool done) {
|
||||
|
||||
BLETxView::BLETxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&button_open,
|
||||
add_children({&dataEditView,
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&progressbar,
|
||||
&check_rand_mac,
|
||||
@@ -340,7 +341,6 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch});
|
||||
@@ -399,6 +399,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
nav,
|
||||
packetFileBuffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
on_save_file(buffer);
|
||||
});
|
||||
@@ -429,6 +430,23 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_change = [this](uint8_t value) {
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
|
||||
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_cursor_moved = [this]() {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
@@ -530,7 +548,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
for (const std::string& str : strings) {
|
||||
dataFile.write(str.c_str(), str.size());
|
||||
dataFile.write("\n", 1);
|
||||
;
|
||||
}
|
||||
|
||||
dataFile.~File();
|
||||
@@ -545,6 +562,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
@@ -216,7 +216,7 @@ class BLETxView : public View {
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
@@ -287,11 +287,8 @@ class BLETxView : public View {
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Console console{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
{0, 9 * 18, screen_width, screen_height - 80}};
|
||||
|
||||
Button button_clear_marked{
|
||||
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
|
||||
|
||||
@@ -109,7 +109,7 @@ class CaptureAppView : public View {
|
||||
/*small*/ true};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
{0 * 8, 2 * 16, screen_width, 1 * 16},
|
||||
u"BBD_????.*",
|
||||
captures_dir,
|
||||
RecordView::FileType::RawS16,
|
||||
|
||||
@@ -166,7 +166,7 @@ class ERTAppView : public View {
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::ERTPacket,
|
||||
|
||||
@@ -259,7 +259,7 @@ class POCSAGAppView : public View {
|
||||
' ',
|
||||
true /*wrap*/};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
Image image_status{
|
||||
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
|
||||
|
||||
@@ -91,10 +91,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
stop();
|
||||
|
||||
if (!reader->open(u"/WAV/" + file_list[id].native())) {
|
||||
@@ -108,7 +104,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
// button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
uint32_t sample_rate = reader->sample_rate();
|
||||
|
||||
tone_key_index = options_tone_key.selected_index();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
@@ -155,6 +153,23 @@ void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void SoundBoardView::update_config() {
|
||||
// NB: this were called by the on_bandwidth_changed() callback,
|
||||
// so other val would be updated too when bw changed. currently it's safe but be careful.
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
bits_per_sample,
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
}
|
||||
|
||||
void SoundBoardView::on_select_entry() {
|
||||
tx_view.focus();
|
||||
}
|
||||
@@ -289,6 +304,10 @@ SoundBoardView::SoundBoardView(
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_bandwidth_changed = [this]() {
|
||||
update_config();
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
start_tx(menu_view.highlighted_index());
|
||||
};
|
||||
|
||||
@@ -70,6 +70,8 @@ class SoundBoardView : public View {
|
||||
uint32_t playing_id{};
|
||||
uint32_t page = 1;
|
||||
uint32_t c_page = 1;
|
||||
uint32_t tone_key_index = 1;
|
||||
uint8_t bits_per_sample = 1;
|
||||
|
||||
std::vector<std::filesystem::path> file_list{};
|
||||
|
||||
@@ -90,6 +92,7 @@ class SoundBoardView : public View {
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
void refresh_list();
|
||||
void on_select_entry();
|
||||
void update_config();
|
||||
|
||||
Labels labels{
|
||||
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
|
||||
@@ -104,10 +107,10 @@ class SoundBoardView : public View {
|
||||
"<="};
|
||||
|
||||
Text page_info{
|
||||
{0, 29 * 8, 30 * 8, 16}};
|
||||
{0, 29 * 8, screen_width, 16}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 175},
|
||||
{0, 0, screen_width, 175},
|
||||
true};
|
||||
Text text_empty{
|
||||
{7 * 8, 12 * 8, 16 * 8, 16},
|
||||
@@ -128,9 +131,9 @@ class SoundBoardView : public View {
|
||||
{}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 180}};
|
||||
{screen_width - 2 * 8, 180}};
|
||||
Text text_volume_disabled{
|
||||
{28 * 8, 180, 3 * 8, 16},
|
||||
{screen_width - 2 * 8, 180, 3 * 8, 16},
|
||||
"--"};
|
||||
|
||||
Checkbox check_loop{
|
||||
@@ -144,7 +147,7 @@ class SoundBoardView : public View {
|
||||
"Random"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
|
||||
{0 * 8, 31 * 8 + 2, screen_width, 4}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
|
||||
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox",
|
||||
"TommasoVentafridda",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
|
||||
*/
|
||||
|
||||
namespace ui {
|
||||
class AboutView : public View {
|
||||
public:
|
||||
@@ -22,11 +26,11 @@ class AboutView : public View {
|
||||
uint16_t frame_sync_count{0};
|
||||
void on_frame_sync();
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 264},
|
||||
{0, 0, screen_width, screen_height - 56},
|
||||
true};
|
||||
|
||||
Button button_ok{
|
||||
{240 / 3, 270, 240 / 3, 24},
|
||||
{screen_width / 3, screen_height - 50, screen_width / 3, 24},
|
||||
"OK",
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
|
||||
|
||||
static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
||||
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
|
||||
}
|
||||
@@ -70,18 +72,34 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
||||
|
||||
entry_string +=
|
||||
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
|
||||
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
|
||||
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
|
||||
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
|
||||
to_string_dec_uint(entry.age, 4);
|
||||
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
|
||||
|
||||
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
|
||||
// It is generally accepted that for every thousand feet of altitude,
|
||||
// true airspeed is approximately 2% higher than indicated airspeed.
|
||||
// Since the application CPU has no floating point unit, we avoid floating point here
|
||||
// tas = entry.velo.speed + (float)entry.pos.altitude / 1000.0 * 0.02 * entry.velo.speed;
|
||||
unsigned int tas = entry.velo.speed + entry.pos.altitude * 2 * entry.velo.speed / 100000;
|
||||
|
||||
entry_string +=
|
||||
to_string_dec_uint(tas, 4) + '*' +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 3);
|
||||
} else {
|
||||
entry_string +=
|
||||
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4);
|
||||
}
|
||||
|
||||
entry_string += " " +
|
||||
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
|
||||
to_string_dec_uint(entry.age, 4);
|
||||
|
||||
painter.draw_string(
|
||||
target_rect.location(),
|
||||
style,
|
||||
entry_string);
|
||||
|
||||
if (entry.pos.valid)
|
||||
if (entry.pos.pos_valid)
|
||||
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
|
||||
bitmap_target, target_color, style.background);
|
||||
}
|
||||
@@ -93,22 +111,31 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
||||
log_line.reserve(100);
|
||||
|
||||
log_line = log_entry.raw_data;
|
||||
log_line += "ICAO:" + log_entry.icao;
|
||||
log_line += " ICAO:" + log_entry.icao;
|
||||
|
||||
if (log_entry.sqwk)
|
||||
log_line += " Squawk:" + to_string_dec_uint(log_entry.sqwk, 4, '0');
|
||||
|
||||
if (!log_entry.callsign.empty())
|
||||
log_line += " " + log_entry.callsign;
|
||||
|
||||
if (log_entry.pos.valid)
|
||||
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
|
||||
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
|
||||
if (log_entry.pos.alt_valid)
|
||||
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude);
|
||||
|
||||
if (log_entry.pos.pos_valid)
|
||||
log_line += " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
|
||||
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
|
||||
|
||||
if (log_entry.vel.valid)
|
||||
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
|
||||
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
|
||||
" Spd: " + to_string_dec_int(log_entry.vel.speed);
|
||||
speed_type_msg[log_entry.vel.type] +
|
||||
to_string_dec_int(log_entry.vel.speed) +
|
||||
" Vrate:" + to_string_dec_int(log_entry.vel.v_rate);
|
||||
|
||||
if (log_entry.sil != 0)
|
||||
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
|
||||
|
||||
log_file.write_entry(log_line);
|
||||
}
|
||||
|
||||
@@ -258,7 +285,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
get_map_tag(entry_),
|
||||
entry_.pos.altitude,
|
||||
GeoPos::alt_unit::FEET,
|
||||
GeoPos::spd_unit::MPH,
|
||||
GeoPos::spd_unit::HIDDEN,
|
||||
entry_.pos.latitude,
|
||||
entry_.pos.longitude,
|
||||
entry_.velo.heading);
|
||||
@@ -356,11 +383,12 @@ void ADSBRxDetailsView::refresh_ui() {
|
||||
text_callsign.set(entry_.callsign);
|
||||
text_infos.set(entry_.info_string);
|
||||
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
|
||||
std::string str_sqw = (entry_.sqwk > 0) ? " Sqw:" + to_string_dec_uint(entry_.sqwk) : "";
|
||||
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
|
||||
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
|
||||
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
|
||||
speed_type_msg[entry_.velo.type] + to_string_dec_int(entry_.velo.speed) + str_sil + str_sqw);
|
||||
else
|
||||
text_info2.set(str_sil);
|
||||
text_info2.set(str_sil + str_sqw);
|
||||
|
||||
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
|
||||
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
|
||||
@@ -381,7 +409,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
&status_good_frame,
|
||||
&field_volume});
|
||||
|
||||
recent_entries_view.set_parent_rect({0, 16, 240, 272});
|
||||
recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
|
||||
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
|
||||
detail_key = entry.key();
|
||||
details_view = nav.push<ADSBRxDetailsView>(entry);
|
||||
@@ -421,14 +449,22 @@ void ADSBRxView::focus() {
|
||||
|
||||
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
auto frame = message->frame;
|
||||
uint32_t ICAO_address = frame.get_ICAO_address();
|
||||
status_frame.toggle();
|
||||
|
||||
// Bad frame, skip it.
|
||||
if (!frame.check_CRC() || ICAO_address == 0)
|
||||
return;
|
||||
uint32_t ICAO_address;
|
||||
uint32_t crc = frame.check_CRC();
|
||||
|
||||
if (crc != 0) {
|
||||
if (find(recent, crc) != recent.end())
|
||||
ICAO_address = crc;
|
||||
else
|
||||
return; // Bad frame, skip it.
|
||||
} else {
|
||||
ICAO_address = frame.get_ICAO_address();
|
||||
if (ICAO_address == 0)
|
||||
return; // Bad frame, skip it.
|
||||
}
|
||||
|
||||
ADSBLogEntry log_entry;
|
||||
status_good_frame.toggle();
|
||||
|
||||
rtc::RTC datetime;
|
||||
@@ -440,42 +476,68 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
entry.inc_hit();
|
||||
entry.reset_age();
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
|
||||
// Store smoothed amplitude on updates.
|
||||
entry.amp = entry.hits == 0
|
||||
? message->amp
|
||||
: ((entry.amp * 15) + message->amp) >> 4;
|
||||
|
||||
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
|
||||
uint8_t df = frame.get_DF();
|
||||
|
||||
if (df == 11) // do not log DF11, because messages arrive too frequently
|
||||
return;
|
||||
|
||||
ADSBLogEntry log_entry;
|
||||
uint8_t* raw_data = frame.get_raw_data();
|
||||
|
||||
if (df & 0x10) // 112 bits
|
||||
log_entry.raw_data = to_string_hex_array(raw_data, 14);
|
||||
else { // 56 bits
|
||||
log_entry.raw_data = to_string_hex_array(raw_data, 7);
|
||||
log_entry.raw_data.append(14, ' ');
|
||||
}
|
||||
|
||||
log_entry.icao = entry.icao_str;
|
||||
|
||||
if (frame.get_DF() == DF_ADSB) {
|
||||
// 17: // Extended squitter
|
||||
// 18: // Extended squitter/non-transponder
|
||||
if (df == DF_ADSB) {
|
||||
uint8_t msg_type = frame.get_msg_type();
|
||||
uint8_t msg_sub = frame.get_msg_sub();
|
||||
uint8_t* raw_data = frame.get_raw_data();
|
||||
|
||||
// 4: // surveillance, altitude reply
|
||||
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
|
||||
// transmitted when horizontal position information is not available but altitude information is available
|
||||
if (msg_type == 0) {
|
||||
// Q-bit must be present
|
||||
if (raw_data[5] & 1) {
|
||||
int altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
|
||||
|
||||
log_entry.pos.altitude = entry.pos.altitude = altitude;
|
||||
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
|
||||
}
|
||||
}
|
||||
// 1-4: Aircraft identification
|
||||
else if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
|
||||
entry.set_callsign(decode_frame_id(frame));
|
||||
log_entry.callsign = entry.callsign;
|
||||
}
|
||||
|
||||
// 9:
|
||||
// 18: // Extended squitter/non-transponder
|
||||
// 21: // Comm-B, identity reply
|
||||
// 20: // Comm-B, altitude reply
|
||||
// 9-18: Airborne position (w/Baro Altitude)
|
||||
// 20-22: Airborne position (w/GNSS Height)
|
||||
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
|
||||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
|
||||
entry.set_frame_pos(frame, raw_data[6] & 4);
|
||||
log_entry.pos = entry.pos;
|
||||
|
||||
if (entry.pos.valid) {
|
||||
if (entry.pos.pos_valid) {
|
||||
std::string str_info =
|
||||
"Alt:" + to_string_dec_int(entry.pos.altitude) +
|
||||
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
|
||||
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
|
||||
entry.set_info_string(std::move(str_info));
|
||||
}
|
||||
|
||||
// 19: Airborne velocities
|
||||
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
|
||||
entry.set_frame_velo(frame);
|
||||
log_entry.vel = entry.velo;
|
||||
@@ -485,11 +547,59 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
}
|
||||
}
|
||||
|
||||
logger->log(log_entry);
|
||||
// 4: // surveillance, altitude reply
|
||||
// 20: // Comm-B, altitude reply
|
||||
// 21: // Comm-B, identity reply
|
||||
if (df == 0 || df == 4 || df == 20) { // Decode the 13 bit AC altitude field
|
||||
uint8_t m_bit = raw_data[3] & (1 << 6);
|
||||
uint8_t q_bit = raw_data[3] & (1 << 4);
|
||||
int altitude = 0;
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
if (!m_bit) { // units -> FEET
|
||||
if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M
|
||||
int n = ((raw_data[2] & 31) << 6) |
|
||||
((raw_data[3] & 0x80) >> 2) |
|
||||
((raw_data[3] & 0x20) >> 1) |
|
||||
(raw_data[3] & 15);
|
||||
|
||||
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
|
||||
altitude = 25 * n - 1000;
|
||||
if (altitude < 0)
|
||||
altitude = 0;
|
||||
} // else N is an 11 bit Gillham coded altitude
|
||||
}
|
||||
|
||||
log_entry.pos.altitude = entry.pos.altitude = altitude;
|
||||
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
|
||||
}
|
||||
|
||||
if (df == 5 || df == 21 ||
|
||||
(df == 17 && frame.get_msg_type() == 28 && frame.get_msg_sub() == 1)) { // Decode the squawk code
|
||||
uint8_t* s = (df == 17) ? raw_data + 5 : raw_data + 2; // calc start of the code
|
||||
uint16_t sqwk{0};
|
||||
|
||||
sqwk = ((s[1] & 0x80) >> 5) | ((s[0] & 0x02) >> 0) | ((s[0] & 0x08) >> 3); // A
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[1] & 0x02) << 1) | ((s[1] & 0x08) >> 2) | ((s[1] & 0x20) >> 5); // B
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[0] & 0x01) << 2) | ((s[0] & 0x04) >> 1) | ((s[0] & 0x10) >> 4); // C
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[1] & 0x01) << 2) | ((s[1] & 0x04) >> 1) | ((s[1] & 0x10) >> 4); // D
|
||||
|
||||
log_entry.sqwk = entry.sqwk = sqwk;
|
||||
}
|
||||
|
||||
if (df == 20 || df == 21) {
|
||||
if (raw_data[4] == 0x20) { // try decode as BDS20
|
||||
std::string callsign = decode_frame_id(frame);
|
||||
if (callsign.find('#') == std::string::npos) { // all chars OK
|
||||
entry.set_callsign(callsign);
|
||||
log_entry.callsign = callsign;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger->log(log_entry);
|
||||
}
|
||||
|
||||
void ADSBRxView::on_tick_second() {
|
||||
@@ -544,7 +654,7 @@ void ADSBRxView::refresh_ui() {
|
||||
}
|
||||
|
||||
// NB: current entry also gets a marker so it shows up if map is panned.
|
||||
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
|
||||
if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
|
||||
map_needs_update = details_view->add_map_marker(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,8 +95,8 @@ struct AircraftRecentEntry {
|
||||
ADSBAgeState state{ADSBAgeState::Invalid};
|
||||
uint32_t age{0}; // In seconds
|
||||
uint32_t amp{0};
|
||||
adsb_pos pos{false, 0, 0, 0};
|
||||
adsb_vel velo{false, 0, 999, 0};
|
||||
adsb_pos pos{false, false, 0, 0, 0};
|
||||
adsb_vel velo{false, SPD_GND, 0, 999, 0};
|
||||
ADSBFrame frame_pos_even{};
|
||||
ADSBFrame frame_pos_odd{};
|
||||
|
||||
@@ -105,6 +105,7 @@ struct AircraftRecentEntry {
|
||||
std::string info_string{};
|
||||
|
||||
uint8_t sil{0}; // Surveillance integrity level
|
||||
uint16_t sqwk{0};
|
||||
|
||||
AircraftRecentEntry(const uint32_t ICAO_address)
|
||||
: ICAO_address{ICAO_address} {
|
||||
@@ -152,8 +153,8 @@ struct AircraftRecentEntry {
|
||||
age += delta;
|
||||
|
||||
if (age < ADSBAgeLimit::Current)
|
||||
state = pos.valid ? ADSBAgeState::Invalid
|
||||
: ADSBAgeState::Current;
|
||||
state = pos.pos_valid ? ADSBAgeState::Current
|
||||
: ADSBAgeState::Invalid;
|
||||
|
||||
else if (age < ADSBAgeLimit::Recent)
|
||||
state = ADSBAgeState::Recent;
|
||||
@@ -178,6 +179,7 @@ struct ADSBLogEntry {
|
||||
adsb_vel vel{};
|
||||
uint8_t vel_type{};
|
||||
uint8_t sil{};
|
||||
uint16_t sqwk{};
|
||||
};
|
||||
|
||||
// TODO: Make logging optional.
|
||||
@@ -228,7 +230,7 @@ class ADSBRxAircraftDetailsView : public View {
|
||||
"-"};
|
||||
|
||||
Text text_model{
|
||||
{0 * 8, 6 * 16, 30 * 8, 16},
|
||||
{0 * 8, 6 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_type{
|
||||
@@ -236,19 +238,19 @@ class ADSBRxAircraftDetailsView : public View {
|
||||
"-"};
|
||||
|
||||
Text text_number_of_engines{
|
||||
{18 * 8, 8 * 16, 30 * 8, 16},
|
||||
{18 * 8, 8 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_engine_type{
|
||||
{0 * 8, 10 * 16, 30 * 8, 16},
|
||||
{0 * 8, 10 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_owner{
|
||||
{0 * 8, 12 * 16, 30 * 8, 16},
|
||||
{0 * 8, 12 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_operator{
|
||||
{0 * 8, 14 * 16, 30 * 8, 16},
|
||||
{0 * 8, 14 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Button button_close{
|
||||
@@ -310,7 +312,7 @@ class ADSBRxDetailsView : public View {
|
||||
"-"};
|
||||
|
||||
Text text_airline{
|
||||
{0 * 8, 4 * 16, 30 * 8, 16},
|
||||
{0 * 8, 4 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_country{
|
||||
@@ -318,18 +320,18 @@ class ADSBRxDetailsView : public View {
|
||||
"-"};
|
||||
|
||||
Text text_infos{
|
||||
{0 * 8, 6 * 16, 30 * 8, 16},
|
||||
{0 * 8, 6 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_info2{
|
||||
{0 * 8, 7 * 16, 30 * 8, 16},
|
||||
{0 * 8, 7 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Text text_frame_pos_even{
|
||||
{0 * 8, 14 * 16, 30 * 8, 16},
|
||||
{0 * 8, 14 * 16, screen_width, 16},
|
||||
"-"};
|
||||
Text text_frame_pos_odd{
|
||||
{0 * 8, 16 * 16, 30 * 8, 16},
|
||||
{0 * 8, 16 * 16, screen_width, 16},
|
||||
"-"};
|
||||
|
||||
Button button_aircraft_details{
|
||||
@@ -440,7 +442,7 @@ class ADSBRxView : public View {
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame{
|
||||
Message::ID::ADSBFrame,
|
||||
|
||||
@@ -247,8 +247,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
|
||||
|
||||
details_view.hidden(true);
|
||||
|
||||
recent_entries_view.set_parent_rect({0, 0, 240, 280});
|
||||
details_view.set_parent_rect({0, 0, 240, 280});
|
||||
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
|
||||
details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
|
||||
|
||||
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
|
||||
this->on_show_detail(entry);
|
||||
|
||||
@@ -134,7 +134,7 @@ class APRSDetailsView : public View {
|
||||
bool send_updates{false};
|
||||
|
||||
Console console{
|
||||
{0, 0 * 16, 240, 224}};
|
||||
{0, 0 * 16, screen_width, 224}};
|
||||
|
||||
Button button_done{
|
||||
{160, 14 * 16, 8 * 8, 3 * 16},
|
||||
@@ -220,7 +220,7 @@ class APRSRxView : public View {
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
OptionsField options_region{
|
||||
{0 * 8, 0 * 8},
|
||||
@@ -241,7 +241,7 @@ class APRSRxView : public View {
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view{
|
||||
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
|
||||
{0 * 8, 1 * 16, screen_width, 1 * 16},
|
||||
u"AFS_????.WAV",
|
||||
aprs_dir,
|
||||
RecordView::FileType::WAV,
|
||||
@@ -249,7 +249,7 @@ class APRSRxView : public View {
|
||||
4};
|
||||
|
||||
Console console{
|
||||
{0, 2 * 16, 240, 240}};
|
||||
{0, 2 * 16, screen_width, screen_height - 80}};
|
||||
|
||||
std::unique_ptr<APRSLogger> logger{};
|
||||
};
|
||||
@@ -265,7 +265,7 @@ class APRSRXView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Rect view_rect = {0, 3 * 8, 240, 280};
|
||||
Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
|
||||
|
||||
APRSRxView view_stream{nav_, view_rect};
|
||||
APRSTableView view_table{nav_, view_rect};
|
||||
|
||||
@@ -94,6 +94,7 @@ APRSTXView::APRSTXView(NavigationView& nav) {
|
||||
nav,
|
||||
payload,
|
||||
30,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
text_payload.set(s);
|
||||
});
|
||||
|
||||
@@ -91,7 +91,7 @@ class APRSTXView : public View {
|
||||
' '};
|
||||
|
||||
Text text_payload{
|
||||
{0 * 8, 5 * 16, 30 * 8, 16},
|
||||
{0 * 8, 5 * 16, screen_width, 16},
|
||||
"-"};
|
||||
Button button_set{
|
||||
{0 * 8, 6 * 16, 80, 32},
|
||||
|
||||
@@ -90,7 +90,7 @@ class BattinfoView : public View {
|
||||
"-"};
|
||||
|
||||
Text text_warn{
|
||||
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
|
||||
{1 * 8, 8 * 16, screen_width, 2 * 16},
|
||||
""}; */
|
||||
|
||||
Button button_mode{
|
||||
|
||||
@@ -189,7 +189,7 @@ class BHTView : public View {
|
||||
|
||||
tx_modes tx_mode = IDLE;
|
||||
|
||||
Rect view_rect = {0, 3 * 8, 240, 176};
|
||||
Rect view_rect = {0, 3 * 8, screen_width, 176};
|
||||
|
||||
XylosView view_xylos{view_rect};
|
||||
EPARView view_EPAR{view_rect};
|
||||
@@ -218,7 +218,7 @@ class BHTView : public View {
|
||||
' '};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 29 * 8, 30 * 8, 16},
|
||||
{0 * 8, 29 * 8, screen_width, 16},
|
||||
};
|
||||
|
||||
TransmitterView tx_view{
|
||||
|
||||
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_{};
|
||||
BMPViewer bmp{{0, 0, 240, 320}};
|
||||
BMPViewer bmp{{0, 0, screen_width, screen_height}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -78,11 +78,11 @@ class BTLERxView : public View {
|
||||
nav_};
|
||||
|
||||
Button button_modem_setup{
|
||||
{240 - 12 * 8, 1 * 16, 96, 24},
|
||||
{screen_width - 12 * 8, 1 * 16, 96, 24},
|
||||
"Modem setup"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
{0, 4 * 16, screen_width, screen_height - 80}};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
|
||||
@@ -202,10 +202,10 @@ RegistersView::RegistersView(
|
||||
};
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
|
||||
registers_widget.set_parent_rect({0, 48, 240, 192});
|
||||
registers_widget.set_parent_rect({0, 48, screen_width, 192});
|
||||
registers_widget.set_page(0);
|
||||
|
||||
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
|
||||
text_title.set_parent_rect({(screen_width - static_cast<int>(title.size()) * 8) / 2, 16,
|
||||
static_cast<int>(title.size()) * 8, 16});
|
||||
text_title.set(title);
|
||||
|
||||
@@ -419,7 +419,6 @@ void DebugMenuView::on_populate() {
|
||||
}
|
||||
add_items({
|
||||
{"Buttons Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_controls, [this]() { nav_.push<DebugControlsView>(); }},
|
||||
{"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
|
||||
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
|
||||
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
|
||||
@@ -483,7 +482,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
|
||||
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
|
||||
add_children({®isters_widget, &text_checksum, &text_checksum2, &button_ok});
|
||||
|
||||
registers_widget.set_parent_rect({0, 32, 240, 192});
|
||||
registers_widget.set_parent_rect({0, 32, screen_width, 192});
|
||||
|
||||
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
|
||||
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
|
||||
|
||||
@@ -283,7 +283,7 @@ class DebugMemoryDumpView : public View {
|
||||
"Write"};
|
||||
|
||||
Button button_done{
|
||||
{128, 240, 96, 24},
|
||||
{128, screen_height - 80, 96, 24},
|
||||
"Done"};
|
||||
|
||||
Labels labels{
|
||||
@@ -332,7 +332,7 @@ class DebugPmemView : public View {
|
||||
Text text_checksum2{{16, 248, 208, 16}};
|
||||
|
||||
Button button_ok{
|
||||
{240 / 3, 270, 240 / 3, 24},
|
||||
{screen_width / 3, 270, screen_width / 3, 24},
|
||||
"OK",
|
||||
};
|
||||
|
||||
@@ -364,7 +364,7 @@ public:
|
||||
|
||||
private:
|
||||
Console console {
|
||||
{ 8, 16, 224, 240 }
|
||||
{ 8, 16, 224, screen_height-80 }
|
||||
};
|
||||
|
||||
Button button_exit {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "encoders.hpp"
|
||||
#include "de_bruijn.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
@@ -125,7 +124,7 @@ class EncodersConfigView : public View {
|
||||
""};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 17 * 8, 240, 32},
|
||||
{0, 17 * 8, screen_width, 32},
|
||||
waveform_buffer,
|
||||
0,
|
||||
0,
|
||||
@@ -202,7 +201,7 @@ class EncodersView : public View {
|
||||
void start_tx(const bool scan);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
Rect view_rect = {0, 4 * 8, 240, 168};
|
||||
Rect view_rect = {0, 4 * 8, screen_width, 168};
|
||||
|
||||
EncodersConfigView view_config{nav_, view_rect};
|
||||
EncodersScanView view_scan{nav_, view_rect};
|
||||
|
||||
@@ -92,6 +92,9 @@ void ExternalModuleView::on_tick_second() {
|
||||
case app_location_t::TX:
|
||||
btnText += " (TX)";
|
||||
break;
|
||||
case app_location_t::TRX:
|
||||
btnText += " (TRX)";
|
||||
break;
|
||||
case app_location_t::SETTINGS:
|
||||
btnText += " (Settings)";
|
||||
break;
|
||||
|
||||
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
|
||||
Text text_app5_name{{24, 160, 200, 16}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
{screen_width, 0, 0, 0},
|
||||
""};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
// Collect all entries first
|
||||
std::list<fileman_entry> all_entries;
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
insert_sorted(all_entries, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// paginating
|
||||
auto list_size = entry_list.size();
|
||||
nb_pages = 1 + (list_size / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (list_size > start) {
|
||||
if (list_size < stop)
|
||||
stop = list_size;
|
||||
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
|
||||
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
|
||||
// Calculate pagination
|
||||
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
|
||||
if (nb_pages == 0) nb_pages = 1;
|
||||
|
||||
size_t start_idx = pagination * items_per_page;
|
||||
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
|
||||
|
||||
// Add "parent" directory if not at the root and on first page
|
||||
if (!dir_path.empty() && pagination == 0) {
|
||||
entry_list.push_back({parent_dir_path.string(), 0, true});
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (list_size > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
// Add prev page navigation if not on first page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
|
||||
// Add entries for current page
|
||||
auto it = all_entries.begin();
|
||||
std::advance(it, start_idx);
|
||||
|
||||
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
|
||||
entry_list.push_back(*it);
|
||||
}
|
||||
|
||||
// Add next page navigation if not on last page
|
||||
if (end_idx < all_entries.size()) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,30 +326,49 @@ void FileManBaseView::focus() {
|
||||
} else {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
// Set menu to the correct page and select the correct item
|
||||
menu_view.set_highlighted(prev_highlight % items_per_page);
|
||||
}
|
||||
|
||||
// Push directory - store the global index (page * items_per_page + local_index)
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
// Save global index (combines page number and item position)
|
||||
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
|
||||
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(true);
|
||||
reload_current(true); // Reset pagination when entering new directory
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
if (current_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to parent directory
|
||||
current_path = current_path.parent_path();
|
||||
reload_current(true);
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Restore the previous global index if available
|
||||
if (!saved_index_stack.empty()) {
|
||||
uint32_t global_index = saved_index_stack.back();
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Calculate pagination from global index
|
||||
pagination = global_index / items_per_page;
|
||||
|
||||
// Calculate local index within the page
|
||||
prev_highlight = global_index % items_per_page;
|
||||
restoring_navigation = true;
|
||||
}
|
||||
|
||||
reload_current(false); // Important: don't reset pagination
|
||||
}
|
||||
|
||||
std::string get_extension(std::string t) {
|
||||
std::string FileManBaseView::get_extension(const std::string& t) const {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return {};
|
||||
@@ -372,7 +397,9 @@ void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
if (!restoring_navigation) {
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
}
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
@@ -411,14 +438,29 @@ void FileManBaseView::refresh_list() {
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
restoring_navigation = false;
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
if (reset_pagination) pagination = 0;
|
||||
// Only reset pagination if explicitly requested
|
||||
if (reset_pagination) {
|
||||
pagination = 0;
|
||||
}
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void FileManBaseView::copy_waterfall(std::filesystem::path path) {
|
||||
nav_.push<ModalMessageView>(
|
||||
"Install", " Use this gradient file\n for all waterfalls?", YESNO,
|
||||
[this, path](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(default_gradient_file);
|
||||
copy_file(path, default_gradient_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const {
|
||||
size_t index = 0;
|
||||
@@ -444,7 +486,7 @@ FileLoadView::FileLoadView(
|
||||
add_children({&menu_view});
|
||||
|
||||
// Resize menu view to fill screen
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
|
||||
|
||||
refresh_list();
|
||||
|
||||
@@ -478,63 +520,6 @@ void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* FileSaveView **************************************************************/
|
||||
/*
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
const fs::path& file
|
||||
) : nav_{ nav },
|
||||
path_{ path },
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
&button_edit_name,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
button_edit_path.on_select = [this](Button&) {
|
||||
buffer_ = path_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
path_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_edit_name.on_select = [this](Button&) {
|
||||
buffer_ = file_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
file_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(path_ / file_);
|
||||
else
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_widgets();
|
||||
}
|
||||
|
||||
void FileSaveView::refresh_widgets() {
|
||||
text_path.set(truncate(path_, 30));
|
||||
text_name.set(truncate(file_, 30));
|
||||
set_dirty();
|
||||
}
|
||||
*/
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
@@ -566,7 +551,7 @@ void FileManagerView::on_rename(std::string_view hint) {
|
||||
cursor_pos = pos;
|
||||
|
||||
text_prompt(
|
||||
nav_, name_buffer, cursor_pos, max_filename_length,
|
||||
nav_, name_buffer, cursor_pos, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& renamed) {
|
||||
auto renamed_path = fs::path{renamed};
|
||||
rename_file(get_selected_full_path(), current_path / renamed_path);
|
||||
@@ -640,7 +625,7 @@ void FileManagerView::on_clean() {
|
||||
|
||||
void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current(true);
|
||||
});
|
||||
@@ -671,7 +656,7 @@ void FileManagerView::on_paste() {
|
||||
|
||||
void FileManagerView::on_new_file() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
||||
text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& file_name) {
|
||||
make_new_file(current_path / file_name);
|
||||
reload_current(true);
|
||||
});
|
||||
@@ -685,7 +670,11 @@ bool FileManagerView::handle_file_open() {
|
||||
auto ext = path.extension();
|
||||
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
if (path_iequal(current_path, u"/" + waterfalls_dir)) {
|
||||
copy_waterfall(path);
|
||||
} else {
|
||||
nav_.push<TextEditorView>(path);
|
||||
}
|
||||
return true;
|
||||
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
|
||||
@@ -64,6 +64,7 @@ class FileManBaseView : public View {
|
||||
uint32_t prev_highlight = 0;
|
||||
uint8_t pagination = 0;
|
||||
uint8_t nb_pages = 1;
|
||||
bool restoring_navigation = false;
|
||||
static constexpr size_t max_filename_length = 20;
|
||||
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
|
||||
static constexpr size_t items_per_page = 20;
|
||||
@@ -96,6 +97,8 @@ class FileManBaseView : public View {
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
std::string get_extension(const std::string& t) const;
|
||||
void copy_waterfall(std::filesystem::path path);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -124,7 +127,7 @@ class FileManBaseView : public View {
|
||||
};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, 2 * 8, screen_width, 26 * 8},
|
||||
true};
|
||||
|
||||
Button button_exit{
|
||||
@@ -171,7 +174,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_path {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 2 * 16, screen_width, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
@@ -181,7 +184,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_name {
|
||||
{ 0 * 8, 7 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 7 * 16, screen_width, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
|
||||
|
||||
ensure_directory(apps_dir);
|
||||
ensure_directory(firmware_dir);
|
||||
|
||||
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
|
||||
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, 2 * 8, screen_width, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
|
||||
@@ -45,8 +45,8 @@ using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_modulations;
|
||||
extern options_db_t freqman_bandwidths[5];
|
||||
extern options_db_t freqman_steps;
|
||||
extern options_db_t freqman_bandwidths[6];
|
||||
// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
|
||||
extern options_db_t freqman_steps_short;
|
||||
|
||||
options_t dboptions_to_options(const options_db_t& dboptions) {
|
||||
@@ -240,7 +240,7 @@ void FrequencyManagerView::on_edit_freq() {
|
||||
|
||||
void FrequencyManagerView::on_edit_desc() {
|
||||
temp_buffer_ = current_entry().description;
|
||||
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_desc) {
|
||||
auto entry = current_entry();
|
||||
entry.description = std::move(new_desc);
|
||||
db_.replace_entry(current_index(), entry);
|
||||
@@ -250,7 +250,7 @@ void FrequencyManagerView::on_edit_desc() {
|
||||
|
||||
void FrequencyManagerView::on_add_category() {
|
||||
temp_buffer_.clear();
|
||||
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
|
||||
text_prompt(nav_, temp_buffer_, 20, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
|
||||
if (!new_name.empty()) {
|
||||
create_freqman_file(new_name);
|
||||
refresh_categories();
|
||||
|
||||
@@ -100,7 +100,7 @@ class FrequencySaveView : public FreqManBaseView {
|
||||
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
TextField field_description{
|
||||
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
|
||||
{0 * 8, 7 * 16, screen_width, 1 * 16},
|
||||
""};
|
||||
|
||||
Button button_save{
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace ui {
|
||||
|
||||
IQTrimView::IQTrimView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
power_buckets_.resize(screen_width);
|
||||
add_children({
|
||||
&labels,
|
||||
&field_path,
|
||||
|
||||
@@ -96,7 +96,7 @@ class IQTrimView : public View {
|
||||
|
||||
std::filesystem::path path_{};
|
||||
Optional<iq::CaptureInfo> info_{};
|
||||
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
|
||||
std::vector<iq::PowerBuckets::Bucket> power_buckets_{};
|
||||
TrimProgressUI progress_ui{};
|
||||
|
||||
Labels labels{
|
||||
@@ -112,7 +112,7 @@ class IQTrimView : public View {
|
||||
};
|
||||
|
||||
TextField field_path{
|
||||
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
|
||||
{0 * 8, 1 * 16, screen_width, 1 * 16},
|
||||
"Open File..."};
|
||||
|
||||
Point pos_lines{0 * 8, 4 * 16};
|
||||
|
||||
@@ -94,10 +94,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
|
||||
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
|
||||
rf::Frequency freq_at_pos = 0;
|
||||
if (mode == LOOKING_GLASS_SINGLEPASS) {
|
||||
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
|
||||
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
|
||||
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
|
||||
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
|
||||
} else
|
||||
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
|
||||
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
|
||||
return freq_at_pos;
|
||||
}
|
||||
|
||||
@@ -122,15 +122,15 @@ void GlassView::reset_live_view() {
|
||||
|
||||
// Clear screen in peak mode.
|
||||
if (live_frequency_view == 2)
|
||||
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
|
||||
}
|
||||
|
||||
void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
spectrum_row[pixel_index] = spectrum_rgb3_lut[power]; // row of colors
|
||||
spectrum_row[pixel_index] = gradient.lut[power]; // row of colors
|
||||
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
|
||||
pixel_index++;
|
||||
|
||||
if (pixel_index == SCREEN_W) // got an entire waterfall line
|
||||
if (pixel_index == screen_width) // got an entire waterfall line
|
||||
{
|
||||
if (live_frequency_view > 0) {
|
||||
constexpr int rssi_sample_range = SPEC_NB_BINS;
|
||||
@@ -140,22 +140,22 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
||||
constexpr int raw_delta = raw_max - raw_min;
|
||||
const range_t<int> y_max_range{0, 320 - (108 + 16)};
|
||||
const range_t<int> y_max_range{0, screen_height - (108 + 16)};
|
||||
|
||||
// drawing and keeping track of max freq
|
||||
for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
|
||||
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
|
||||
// save max powerwull freq
|
||||
if (spectrum_data[xpos] > max_freq_power) {
|
||||
max_freq_power = spectrum_data[xpos];
|
||||
max_freq_hold = get_freq_from_bin_pos(xpos);
|
||||
}
|
||||
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
|
||||
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
|
||||
uint8_t color_gradient = (point * 255) / 212;
|
||||
// clear if not in peak view
|
||||
if (live_frequency_view != 2) {
|
||||
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
|
||||
display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
|
||||
}
|
||||
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
|
||||
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
|
||||
}
|
||||
if (last_max_freq != max_freq_hold) {
|
||||
last_max_freq = max_freq_hold;
|
||||
@@ -163,7 +163,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
}
|
||||
plot_marker(marker_pixel_index);
|
||||
} else {
|
||||
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
}
|
||||
pixel_index = 0; // Start New cascade line
|
||||
}
|
||||
@@ -199,7 +199,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
|
||||
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
baseband::spectrum_streaming_stop();
|
||||
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
|
||||
// we actually need SCREEN_W (240) of those bins
|
||||
// we actually need screen_width (240) of those bins
|
||||
for (uint8_t bin = 0; bin < bin_length; bin++) {
|
||||
get_max_power(spectrum, bin, max_power);
|
||||
if (max_power > range_max_power)
|
||||
@@ -238,7 +238,7 @@ void GlassView::on_hide() {
|
||||
}
|
||||
|
||||
void GlassView::on_show() {
|
||||
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
@@ -253,11 +253,11 @@ void GlassView::on_range_changed() {
|
||||
// if the view is done in one pass, show it like in analog_audio_app
|
||||
mode = LOOKING_GLASS_SINGLEPASS;
|
||||
offset = 2;
|
||||
bin_length = SCREEN_W;
|
||||
bin_length = screen_width;
|
||||
ignore_dc = 0;
|
||||
looking_glass_bandwidth = looking_glass_range;
|
||||
looking_glass_sampling_rate = looking_glass_range;
|
||||
each_bin_size = looking_glass_bandwidth / SCREEN_W;
|
||||
each_bin_size = looking_glass_bandwidth / screen_width;
|
||||
looking_glass_step = looking_glass_bandwidth;
|
||||
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
||||
} else {
|
||||
@@ -269,7 +269,7 @@ void GlassView::on_range_changed() {
|
||||
if (mode == LOOKING_GLASS_FASTSCAN) {
|
||||
offset = 2;
|
||||
ignore_dc = 4;
|
||||
bin_length = SCREEN_W;
|
||||
bin_length = screen_width;
|
||||
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
|
||||
offset = 2;
|
||||
bin_length = 80;
|
||||
@@ -279,7 +279,7 @@ void GlassView::on_range_changed() {
|
||||
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
||||
}
|
||||
search_span = looking_glass_range / MHZ_DIV;
|
||||
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
|
||||
marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
|
||||
|
||||
pixel_index = 0;
|
||||
max_power = 0;
|
||||
@@ -304,10 +304,10 @@ void GlassView::plot_marker(uint8_t pos) {
|
||||
{
|
||||
shift_y = 16;
|
||||
}
|
||||
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
|
||||
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
|
||||
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
|
||||
portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
|
||||
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
|
||||
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
|
||||
}
|
||||
|
||||
void GlassView::update_min(int32_t v) {
|
||||
@@ -358,6 +358,11 @@ GlassView::GlassView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
spectrum_row.resize(screen_width);
|
||||
spectrum_data.resize(screen_width);
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
@@ -433,11 +438,11 @@ GlassView::GlassView(
|
||||
freq_stats.hidden(true);
|
||||
button_jump.hidden(true);
|
||||
button_rst.hidden(true);
|
||||
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
|
||||
break;
|
||||
|
||||
case 1: // LEVEL
|
||||
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
|
||||
display.scroll_disable();
|
||||
level_integration.hidden(false);
|
||||
freq_stats.hidden(false);
|
||||
@@ -447,7 +452,7 @@ GlassView::GlassView(
|
||||
|
||||
case 2: // PEAK
|
||||
default:
|
||||
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
|
||||
display.scroll_disable();
|
||||
level_integration.hidden(false);
|
||||
freq_stats.hidden(false);
|
||||
@@ -487,9 +492,9 @@ GlassView::GlassView(
|
||||
|
||||
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
|
||||
if ((marker_pixel_index + delta) < 0)
|
||||
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
|
||||
else if ((marker_pixel_index + delta) > SCREEN_W)
|
||||
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
|
||||
marker_pixel_index = marker_pixel_index + delta + screen_width;
|
||||
else if ((marker_pixel_index + delta) > screen_width)
|
||||
marker_pixel_index = marker_pixel_index + delta - screen_width;
|
||||
else
|
||||
marker_pixel_index = marker_pixel_index + delta;
|
||||
on_marker_change();
|
||||
@@ -527,7 +532,7 @@ GlassView::GlassView(
|
||||
};
|
||||
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||
|
||||
display.scroll_set_area(109, 319);
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||
|
||||
// trigger:
|
||||
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
|
||||
@@ -535,7 +540,7 @@ GlassView::GlassView(
|
||||
// at which time it pushes the buffer up with channel_spectrum.feed
|
||||
baseband::set_spectrum(looking_glass_bandwidth, trigger);
|
||||
|
||||
marker_pixel_index = SCREEN_W / 2;
|
||||
marker_pixel_index = screen_width / 2;
|
||||
on_range_changed(); // Force a UI update.
|
||||
|
||||
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -51,9 +51,6 @@ namespace ui {
|
||||
#define LOOKING_GLASS_SINGLEPASS 2
|
||||
// one spectrum line number of bins
|
||||
#define SPEC_NB_BINS 256
|
||||
// screen dimensions
|
||||
#define SCREEN_W 240
|
||||
#define SCREEN_H 320
|
||||
|
||||
class GlassView : public View {
|
||||
public:
|
||||
@@ -74,6 +71,7 @@ class GlassView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||
// Settings
|
||||
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
|
||||
@@ -154,8 +152,8 @@ class GlassView : public View {
|
||||
uint8_t min_color_power{0}; // Filter cutoff level.
|
||||
uint32_t pixel_index{0};
|
||||
|
||||
std::array<Color, SCREEN_W> spectrum_row{};
|
||||
std::array<uint8_t, SCREEN_W> spectrum_data{};
|
||||
std::vector<Color> spectrum_row{};
|
||||
std::vector<uint8_t> spectrum_data{};
|
||||
ChannelSpectrumFIFO* fifo{};
|
||||
|
||||
int32_t steps = 1;
|
||||
@@ -167,7 +165,7 @@ class GlassView : public View {
|
||||
rf::Frequency max_freq_hold = 0;
|
||||
rf::Frequency last_max_freq = 0;
|
||||
int16_t max_freq_power = -1000;
|
||||
uint8_t bin_length = SCREEN_W;
|
||||
uint8_t bin_length = screen_width;
|
||||
uint8_t offset = 0;
|
||||
uint8_t ignore_dc = 0;
|
||||
|
||||
@@ -221,7 +219,7 @@ class GlassView : public View {
|
||||
{}};
|
||||
|
||||
ButtonWithEncoder button_beep_squelch{
|
||||
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
|
||||
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
|
||||
""};
|
||||
|
||||
TextField field_marker{
|
||||
@@ -292,15 +290,15 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
Button button_jump{
|
||||
{SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
|
||||
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
|
||||
"JMP"};
|
||||
|
||||
Button button_rst{
|
||||
{SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
|
||||
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
|
||||
"RST"};
|
||||
|
||||
Text freq_stats{
|
||||
{0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
|
||||
{0 * 8, 5 * 16, screen_width - 10 * 8, 8},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config{
|
||||
|
||||
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
|
||||
break;
|
||||
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
|
||||
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
|
||||
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
|
||||
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
|
||||
break;
|
||||
case MIC_MOD_WFM:
|
||||
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
|
||||
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
|
||||
break;
|
||||
case MIC_MOD_AM:
|
||||
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
|
||||
|
||||
@@ -350,7 +350,7 @@ class MicTXView : public View {
|
||||
};
|
||||
|
||||
Button tx_button{
|
||||
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
||||
{10 * 8, screen_width, 10 * 8, 5 * 8},
|
||||
"PTT TX",
|
||||
true};
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ class PlaylistView : public View {
|
||||
void handle_replay_thread_done(uint32_t return_code);
|
||||
|
||||
Text text_filename{
|
||||
{0 * 8, 0 * 16, 30 * 8, 16}};
|
||||
{0 * 8, 0 * 16, screen_width, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{0 * 8, 1 * 16}};
|
||||
@@ -139,13 +139,13 @@ class PlaylistView : public View {
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
Text text_track{
|
||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||
{0 * 8, 3 * 16, screen_width, 16}};
|
||||
|
||||
NewButton button_prev{
|
||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
|
||||
@@ -153,7 +153,7 @@ void POCSAGTXView::paint(Painter&) {
|
||||
}
|
||||
|
||||
void POCSAGTXView::on_set_text(NavigationView& nav) {
|
||||
text_prompt(nav, buffer, MAX_POCSAG_LENGTH);
|
||||
text_prompt(nav, buffer, MAX_POCSAG_LENGTH, ENTER_KEYBOARD_MODE_ALPHA);
|
||||
}
|
||||
|
||||
POCSAGTXView::POCSAGTXView(
|
||||
|
||||
@@ -122,10 +122,10 @@ class POCSAGTXView : public View {
|
||||
}};
|
||||
|
||||
Text text_message{
|
||||
{0 * 8, 16 * 8, 30 * 8, 16},
|
||||
{0 * 8, 16 * 8, screen_width, 16},
|
||||
""};
|
||||
Text text_message_l2{
|
||||
{0 * 8, 18 * 8, 30 * 8, 16},
|
||||
{0 * 8, 18 * 8, screen_width, 16},
|
||||
""};
|
||||
|
||||
Button button_message{
|
||||
|
||||
@@ -65,6 +65,7 @@ RDSPSNView::RDSPSNView(
|
||||
nav,
|
||||
PSN,
|
||||
8,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
text_psn.set(s);
|
||||
});
|
||||
@@ -86,6 +87,7 @@ RDSRadioTextView::RDSRadioTextView(
|
||||
nav,
|
||||
radiotext,
|
||||
28,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
text_radiotext.set(s);
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ class RDSView : public View {
|
||||
|
||||
void start_tx();
|
||||
|
||||
Rect view_rect = {0, 8 * 8, 240, 192};
|
||||
Rect view_rect = {0, 8 * 8, screen_width, 192};
|
||||
|
||||
RDSPSNView view_PSN{nav_, view_rect};
|
||||
RDSRadioTextView view_radiotext{nav_, view_rect};
|
||||
|
||||
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
tx_view.hidden(true);
|
||||
|
||||
// set record View
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_AUDIO", audio_dir,
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
@@ -517,11 +517,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
set_loop_config(continuous);
|
||||
|
||||
rssi.set_focusable(true);
|
||||
rssi.set_peak(true, 500);
|
||||
rssi.on_select = [this](RSSI&) {
|
||||
nav_.replace<LevelView>();
|
||||
};
|
||||
|
||||
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
|
||||
button_mic_app.on_select = [this](Button&) {
|
||||
@@ -1168,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"RECON_REPEAT.C16", captures_dir,
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_as_is(true);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_RAW", captures_dir,
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
}
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_AUDIO", audio_dir,
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
@@ -1227,16 +1223,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 48000;
|
||||
break;
|
||||
case AMFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
|
||||
field_bw.set_by_value(0);
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 12000;
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
@@ -1267,9 +1253,17 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
|
||||
field_mode.set_selected_index(new_mod);
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v != -1) {
|
||||
change_mode(v);
|
||||
// initialize to a value under SPEC
|
||||
static freqman_index_t last_mode = WFM_MODULATION;
|
||||
if (v > SPEC_MODULATION) {
|
||||
if (last_mode == SPEC_MODULATION)
|
||||
v = AM_MODULATION;
|
||||
else
|
||||
v = SPEC_MODULATION;
|
||||
field_mode.set_selected_index(v);
|
||||
}
|
||||
last_mode = v;
|
||||
change_mode(v);
|
||||
};
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
@@ -1410,8 +1404,8 @@ void ReconView::start_repeat() {
|
||||
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
|
||||
|
||||
// update display information
|
||||
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
|
||||
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
|
||||
p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
|
||||
p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
|
||||
|
||||
// sleep 1 second
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_looking_glass_app.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
@@ -238,15 +237,15 @@ class ReconView : public View {
|
||||
|
||||
Text file_name{
|
||||
// show file used
|
||||
{0, 1 * 16, SCREEN_W, 16},
|
||||
{0, 1 * 16, screen_width, 16},
|
||||
};
|
||||
|
||||
Text desc_cycle{
|
||||
{0, 2 * 16, SCREEN_W, 16},
|
||||
{0, 2 * 16, screen_width, 16},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
|
||||
{0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
|
||||
};
|
||||
|
||||
ButtonWithEncoder text_cycle{
|
||||
@@ -286,15 +285,15 @@ class ReconView : public View {
|
||||
|
||||
// Button can be RECON or SCANNER
|
||||
Button button_scanner_mode{
|
||||
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
|
||||
"RECON"};
|
||||
|
||||
Button button_loop_config{
|
||||
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
|
||||
"[LOOP]"};
|
||||
|
||||
Button button_config{
|
||||
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
|
||||
"CONFIG"};
|
||||
|
||||
ButtonWithEncoder button_manual_start{
|
||||
@@ -392,7 +391,7 @@ class ReconView : public View {
|
||||
"<REMOVE>"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
|
||||
{0 * 8, screen_height / 2 - 16, screen_width, 32}};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 2 * 16},
|
||||
|
||||
@@ -82,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
};
|
||||
|
||||
button_choose_output_name.on_select = [this, &nav](Button&) {
|
||||
text_prompt(nav, _output_file, 28, [this](std::string& buffer) {
|
||||
text_prompt(nav, _output_file, 28, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& buffer) {
|
||||
_output_file = buffer;
|
||||
button_choose_output_name.set_text(_output_file);
|
||||
});
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
|
||||
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
|
||||
|
||||
// screen size helper
|
||||
#define SCREEN_W 240
|
||||
// #define SCREEN_H 320
|
||||
|
||||
// recon settings nb params
|
||||
#define RECON_SETTINGS_NB_PARAMS 7
|
||||
|
||||
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
|
||||
std::string input_file{"RECON"};
|
||||
std::string output_file{"RECON_RESULTS"};
|
||||
|
||||
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
|
||||
Rect view_rect{0, 3 * 8, screen_width, 230};
|
||||
|
||||
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
|
||||
ReconSetupViewMore viewMore{nav_, view_rect};
|
||||
|
||||
@@ -56,6 +56,10 @@ SearchView::SearchView(
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
&field_frequency_max,
|
||||
@@ -290,7 +294,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
power = spectrum.db[bin - 128];
|
||||
}
|
||||
|
||||
add_spectrum_pixel(spectrum_rgb3_lut[power]);
|
||||
add_spectrum_pixel(gradient.lut[power]);
|
||||
|
||||
mean_acc += power;
|
||||
if (power > max_power) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "receiver_model.hpp"
|
||||
#include "recent_entries.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -88,6 +88,7 @@ class SearchView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{
|
||||
100'000'000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
|
||||
@@ -759,7 +759,7 @@ AppSettingsView::AppSettingsView(
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
|
||||
|
||||
ensure_directory(settings_dir);
|
||||
|
||||
@@ -810,30 +810,29 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_invert_switch,
|
||||
&checkbox_ips_screen_switch,
|
||||
&checkbox_brightness_switch});
|
||||
|
||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
||||
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
|
||||
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
|
||||
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
|
||||
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
|
||||
display.set_inverted(checkbox_invert_switch.value());
|
||||
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
|
||||
if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
|
||||
pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
|
||||
}
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
// only enable invert OR fake brightness
|
||||
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
|
||||
checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_brightness_switch.set_value(false);
|
||||
};
|
||||
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_invert_switch.set_value(false);
|
||||
if (v) checkbox_ips_screen_switch.set_value(false);
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
@@ -1044,12 +1043,14 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_overridebatt});
|
||||
&checkbox_overridebatt,
|
||||
&checkbox_battery_charge_hint});
|
||||
|
||||
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
|
||||
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
|
||||
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
@@ -1064,6 +1065,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
||||
};
|
||||
|
||||
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
|
||||
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
|
||||
@@ -713,7 +713,7 @@ class AppSettingsView : public View {
|
||||
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, 2 * 8, screen_width, 26 * 8},
|
||||
true};
|
||||
};
|
||||
|
||||
@@ -762,6 +762,8 @@ class SetDisplayView : public View {
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_fake_brightness{
|
||||
@@ -776,10 +778,10 @@ class SetDisplayView : public View {
|
||||
16,
|
||||
"Enable brightness adjust"};
|
||||
|
||||
Checkbox checkbox_invert_switch{
|
||||
{1 * 8, 10 * 16},
|
||||
Checkbox checkbox_ips_screen_switch{
|
||||
{1 * 8, 12 * 16},
|
||||
23,
|
||||
"Invert colors (For IPS)"};
|
||||
"IPS Screen"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
@@ -945,13 +947,14 @@ class SetThemeView : public View {
|
||||
|
||||
OptionsField options{
|
||||
{0 * 8, 4 * 16},
|
||||
screen_width / 8,
|
||||
(size_t)(screen_width / 8),
|
||||
{
|
||||
{"Default - Grey", 0},
|
||||
{"Yellow", 1},
|
||||
{"Aqua", 2},
|
||||
{"Green", 3},
|
||||
{"Red", 4},
|
||||
{"Dark", 5},
|
||||
},
|
||||
true};
|
||||
|
||||
@@ -978,9 +981,13 @@ class SetBatteryView : public View {
|
||||
int32_t selected = 0;
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground}};
|
||||
Labels labels2{
|
||||
{{1 * 8, 6 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
{{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
|
||||
/**/
|
||||
{{1 * 8, 6 * 16}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 7 * 16}, "when you charge", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Labels labels2{{{1 * 8, 11 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -990,13 +997,18 @@ class SetBatteryView : public View {
|
||||
23,
|
||||
"Override"};
|
||||
|
||||
Checkbox checkbox_battery_charge_hint{
|
||||
{2 * 8, 9 * 16},
|
||||
23,
|
||||
"Charge hint"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 8 * 16, 12 * 8, 32},
|
||||
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||
"Reset",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
|
||||
uint8_t i, xp;
|
||||
|
||||
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
|
||||
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||
portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||
for (i = 0; i < 6; i++) {
|
||||
xp = sigfrx_marks[i * 3];
|
||||
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
|
||||
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
|
||||
portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
|
||||
|
||||
uint8_t xmax = 0, imax = 0;
|
||||
size_t i;
|
||||
|
||||
@@ -154,6 +154,13 @@ SigGenView::SigGenView(
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_bandwidth_changed = [this]() {
|
||||
// we don't protect here with auto_update because other field of tx_view obj isn't protected too
|
||||
// to remains the design logic same
|
||||
|
||||
update_config();
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
start_tx();
|
||||
tx_view.set_transmitting(true);
|
||||
@@ -165,4 +172,4 @@ SigGenView::SigGenView(
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
@@ -120,7 +120,7 @@ class SondeView : public View {
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
Checkbox check_log{
|
||||
{22 * 8, 8 * 16},
|
||||
|
||||
@@ -58,7 +58,7 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
// Screenshots from PNGWriter are all this size.
|
||||
if (file.size() != 232383) {
|
||||
if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
|
||||
show_invalid();
|
||||
return;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
|
||||
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
|
||||
uint8_t buffer[buffer_size];
|
||||
std::array<Color, screen_width> pixel_data;
|
||||
std::vector<Color> pixel_data(screen_width);
|
||||
|
||||
// Seek past all the headers.
|
||||
file.seek(43);
|
||||
|
||||
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
|
||||
SubGhzDView::SubGhzDView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
|
||||
@@ -129,6 +129,9 @@ class SubGhzDView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
@@ -192,7 +195,7 @@ class SubGhzDRecentEntryDetailView : public View {
|
||||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
|
||||
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
|
||||
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
|
||||
|
||||
cur_x++;
|
||||
if (cur_x >= 240) {
|
||||
display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
|
||||
if (cur_x >= screen_width) {
|
||||
display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
|
||||
cur_x = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,10 +95,10 @@ class TestView : public View {
|
||||
};
|
||||
|
||||
Text text_debug_a{
|
||||
{0 * 8, 4 * 16, 30 * 8, 16},
|
||||
{0 * 8, 4 * 16, screen_width, 16},
|
||||
"..."};
|
||||
Text text_debug_b{
|
||||
{0 * 8, 5 * 16, 30 * 8, 16},
|
||||
{0 * 8, 5 * 16, screen_width, 16},
|
||||
"..."};
|
||||
|
||||
Button button_cal{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "log_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
void TextViewer::enable_long_press() {
|
||||
// Enable long press on "Select".
|
||||
SwitchesState config;
|
||||
config[toUType(Switch::Sel)] = true;
|
||||
set_switches_long_press_config(config);
|
||||
}
|
||||
|
||||
void TextViewer::on_focus() {
|
||||
enable_long_press();
|
||||
}
|
||||
|
||||
bool TextViewer::on_key(const KeyEvent key) {
|
||||
int16_t delta_col = 0;
|
||||
int16_t delta_line = 0;
|
||||
|
||||
if (key == KeyEvent::Left)
|
||||
delta_col = -1;
|
||||
else if (key == KeyEvent::Right)
|
||||
delta_col = 1;
|
||||
else if (key == KeyEvent::Up)
|
||||
delta_line = -1;
|
||||
else if (key == KeyEvent::Down)
|
||||
delta_line = 1;
|
||||
else if (key == KeyEvent::Select && on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
if (key == KeyEvent::Select) {
|
||||
// Toggle 'digit' mode with long-press.
|
||||
if (digit_mode_ || key_is_long_pressed(key)) {
|
||||
digit_mode_ = !digit_mode_;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (digit_mode_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta_col == 0 && delta_line == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
delta_line = -1;
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
delta_line = 1;
|
||||
break;
|
||||
case KeyEvent::Select: {
|
||||
if (on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow cursor direction to be updated.
|
||||
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
|
||||
bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
bool updated = false;
|
||||
|
||||
if (cursor_.dir == ScrollDirection::Horizontal)
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
if (digit_mode_) {
|
||||
if (delta > 0) {
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
} else if (delta < 0) {
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
}
|
||||
|
||||
updated = true;
|
||||
} else {
|
||||
if (cursor_.dir == ScrollDirection::Horizontal) {
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
} else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
redraw();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::set_value(uint8_t new_value) {
|
||||
if (new_value != value_) {
|
||||
value_ = new_value;
|
||||
|
||||
if (on_change) {
|
||||
on_change(value_);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
@@ -617,6 +695,7 @@ void TextEditorView::show_edit_line() {
|
||||
edit_line_buffer_,
|
||||
viewer.col(),
|
||||
max_edit_length,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
auto range = file_->line_range(viewer.line());
|
||||
if (!range)
|
||||
|
||||
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
|
||||
|
||||
std::function<void()> on_select{};
|
||||
std::function<void()> on_cursor_moved{};
|
||||
std::function<void(uint8_t)> on_change{};
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
void set_value(uint8_t new_value);
|
||||
void on_focus() override;
|
||||
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
void enable_long_press();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
bool digit_mode_{false};
|
||||
uint8_t value_{0};
|
||||
bool allow_digit_mode_{true};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
|
||||
@@ -107,6 +107,7 @@ void WeatherView::focus() {
|
||||
WeatherView::WeatherView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
|
||||
@@ -147,9 +147,12 @@ class WeatherView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
|
||||
@@ -93,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
|
||||
|
||||
void WFMConfig::apply() const {
|
||||
const WFMConfigureMessage message{
|
||||
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
|
||||
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
|
||||
taps_64_lp_156_198,
|
||||
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
|
||||
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
|
||||
taps_64_lp_156_198, // Fixed channel audio filter 15khz
|
||||
75000,
|
||||
audio_48k_hpf_30hz_config,
|
||||
audio_48k_deemph_2122_6_config};
|
||||
@@ -103,6 +103,18 @@ void WFMConfig::apply() const {
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
}
|
||||
|
||||
void WFMAMConfig::apply() const {
|
||||
const WFMAMConfigureMessage message{
|
||||
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
|
||||
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
|
||||
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
|
||||
17000, // NOAA satellite tx , FM deviation = +-17Khz.
|
||||
apt_audio_12k_notch_2k4_config,
|
||||
apt_audio_12k_lpf_2000hz_config};
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
|
||||
@@ -174,13 +186,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_fsk(const size_t deviation) {
|
||||
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
|
||||
const FSKRxConfigureMessage message{
|
||||
taps_200k_decim_0,
|
||||
taps_16k0_decim_1,
|
||||
taps_11k0_channel,
|
||||
2,
|
||||
deviation};
|
||||
samplesPerSymbol,
|
||||
syncWord,
|
||||
syncWordLength,
|
||||
preamble,
|
||||
preambleLength,
|
||||
numDataBytes};
|
||||
|
||||
send_message(&message);
|
||||
}
|
||||
@@ -310,6 +323,11 @@ void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_noaaapt_config() {
|
||||
const NoaaAptRxConfigureMessage message{};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_siggen_tone(const uint32_t tone) {
|
||||
const SigGenToneMessage message{
|
||||
TONES_F2D(tone, TONES_SAMPLERATE)};
|
||||
|
||||
@@ -56,12 +56,20 @@ struct NBFMConfig {
|
||||
};
|
||||
|
||||
struct WFMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
|
||||
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
|
||||
const fir_taps_real<16> decim_1;
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
struct WFMAMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
|
||||
const fir_taps_real<32> decim_1;
|
||||
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
@@ -72,7 +80,7 @@ void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
void kill_afsk();
|
||||
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
void set_fsk(const size_t deviation);
|
||||
void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
|
||||
void set_aprs(const uint32_t baudrate);
|
||||
|
||||
void set_btlerx(uint8_t channel_number);
|
||||
@@ -93,6 +101,7 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
|
||||
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
||||
void set_noaaapt_config();
|
||||
|
||||
void request_roger_beep();
|
||||
void request_rssi_beep();
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -105,7 +106,7 @@ void bind(TextField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
|
||||
// Capture a new string and make the lambda mutable so it can be modified.
|
||||
field.on_select = [&nav, buf = std::string{}](TextField& tf) mutable {
|
||||
buf = tf.get_text();
|
||||
text_prompt(nav, buf, /*max_length*/ 255,
|
||||
text_prompt(nav, buf, /*max_length*/ 255, ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[&tf](std::string& str) {
|
||||
tf.set_text(str);
|
||||
});
|
||||
|
||||
+5309
-5233
File diff suppressed because it is too large
Load Diff
@@ -1,72 +0,0 @@
|
||||
unsigned char modal_warning_bmp[] = {
|
||||
0x42, 0x4d, 0xfa, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x00,
|
||||
0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0xa8, 0x02,
|
||||
0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x07, 0x00,
|
||||
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0x00, 0x00, 0x5a,
|
||||
0xff, 0x00, 0x78, 0x78, 0x78, 0x00, 0x00, 0x7c, 0xff, 0x00, 0x00, 0xa9,
|
||||
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x02, 0x03, 0x24, 0x66, 0x02, 0x30,
|
||||
0x00, 0x00, 0x02, 0x36, 0x24, 0x66, 0x02, 0x63, 0x00, 0x00, 0x02, 0x66,
|
||||
0x02, 0x63, 0x20, 0x00, 0x02, 0x36, 0x02, 0x66, 0x00, 0x00, 0x02, 0x36,
|
||||
0x02, 0x60, 0x20, 0x00, 0x02, 0x06, 0x02, 0x63, 0x00, 0x00, 0x02, 0x06,
|
||||
0x02, 0x60, 0x0c, 0x00, 0x00, 0x08, 0x03, 0x66, 0x66, 0x30, 0x0c, 0x00,
|
||||
0x02, 0x06, 0x02, 0x60, 0x00, 0x00, 0x02, 0x03, 0x02, 0x66, 0x0c, 0x00,
|
||||
0x00, 0x08, 0x06, 0x11, 0x11, 0x60, 0x0c, 0x00, 0x02, 0x66, 0x02, 0x30,
|
||||
0x00, 0x00, 0x02, 0x00, 0x02, 0x66, 0x0c, 0x00, 0x00, 0x08, 0x06, 0x21,
|
||||
0x21, 0x60, 0x0c, 0x00, 0x02, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x00, 0x36, 0x60, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x42, 0x42, 0x60,
|
||||
0x0a, 0x00, 0x00, 0x06, 0x06, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x00, 0x06, 0x60, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x54, 0x45, 0x60,
|
||||
0x0a, 0x00, 0x00, 0x06, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x00, 0x03, 0x66, 0x00, 0x0a, 0x00, 0x00, 0x08, 0x03, 0x66, 0x66, 0x30,
|
||||
0x0a, 0x00, 0x00, 0x06, 0x66, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x00, 0x00, 0x66, 0x00, 0x1c, 0x00, 0x00, 0x06, 0x66, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x36, 0x60, 0x18, 0x00, 0x00, 0x08,
|
||||
0x06, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x06, 0x60,
|
||||
0x0a, 0x00, 0x02, 0x36, 0x02, 0x63, 0x0a, 0x00, 0x00, 0x08, 0x06, 0x60,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x66, 0x08, 0x00,
|
||||
0x00, 0x08, 0x03, 0x61, 0x16, 0x30, 0x08, 0x00, 0x00, 0x08, 0x66, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x66, 0x08, 0x00, 0x00, 0x08,
|
||||
0x06, 0x11, 0x11, 0x60, 0x08, 0x00, 0x02, 0x66, 0x06, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x02, 0x36, 0x02, 0x60, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
|
||||
0x11, 0x60, 0x06, 0x00, 0x02, 0x06, 0x02, 0x63, 0x06, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x02, 0x06, 0x02, 0x60, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
|
||||
0x11, 0x60, 0x06, 0x00, 0x02, 0x06, 0x02, 0x60, 0x06, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x02, 0x03, 0x02, 0x66, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11,
|
||||
0x11, 0x60, 0x06, 0x00, 0x02, 0x66, 0x02, 0x30, 0x06, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x02, 0x66, 0x06, 0x00, 0x00, 0x08, 0x06, 0x11, 0x11, 0x60,
|
||||
0x06, 0x00, 0x02, 0x66, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x18,
|
||||
0x36, 0x60, 0x00, 0x00, 0x06, 0x11, 0x11, 0x60, 0x00, 0x00, 0x06, 0x63,
|
||||
0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x18, 0x06, 0x60, 0x00, 0x00,
|
||||
0x06, 0x21, 0x21, 0x60, 0x00, 0x00, 0x06, 0x60, 0x08, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x18, 0x03, 0x66, 0x00, 0x00, 0x06, 0x12, 0x12, 0x60,
|
||||
0x00, 0x00, 0x66, 0x30, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14,
|
||||
0x66, 0x00, 0x00, 0x06, 0x21, 0x21, 0x60, 0x00, 0x00, 0x66, 0x0a, 0x00,
|
||||
0x00, 0x00, 0x0a, 0x00, 0x00, 0x14, 0x36, 0x60, 0x00, 0x06, 0x22, 0x22,
|
||||
0x60, 0x00, 0x06, 0x63, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x14,
|
||||
0x06, 0x60, 0x00, 0x06, 0x24, 0x24, 0x60, 0x00, 0x06, 0x60, 0x0a, 0x00,
|
||||
0x00, 0x00, 0x0a, 0x00, 0x00, 0x14, 0x03, 0x66, 0x00, 0x06, 0x42, 0x42,
|
||||
0x60, 0x00, 0x66, 0x30, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x10,
|
||||
0x66, 0x00, 0x06, 0x54, 0x45, 0x60, 0x00, 0x66, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x00, 0x10, 0x36, 0x60, 0x03, 0x66, 0x66, 0x30, 0x06, 0x63,
|
||||
0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x06, 0x02, 0x60, 0x08, 0x00,
|
||||
0x02, 0x06, 0x02, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x03,
|
||||
0x02, 0x66, 0x08, 0x00, 0x02, 0x66, 0x02, 0x30, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x02, 0x66, 0x08, 0x00, 0x02, 0x66, 0x0e, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x00, 0x0c, 0x36, 0x60, 0x00, 0x00, 0x06, 0x63, 0x0e, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x06, 0x60, 0x00, 0x00, 0x06, 0x60,
|
||||
0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x03, 0x66, 0x00, 0x00,
|
||||
0x66, 0x30, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x66, 0x00,
|
||||
0x00, 0x66, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x36, 0x60,
|
||||
0x06, 0x63, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x06, 0x60,
|
||||
0x06, 0x60, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x03, 0x66,
|
||||
0x66, 0x30, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x66, 0x02, 0x66,
|
||||
0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x36, 0x02, 0x63, 0x12, 0x00,
|
||||
0x00, 0x01};
|
||||
unsigned int modal_warning_bmp_len = 830;
|
||||
@@ -1,327 +0,0 @@
|
||||
// converted by bmp_to_hex_cpp_arr.py at the firmware/tools dir
|
||||
// fake transparent px should be the exact color rgb dec(41,24,22), then use true for the last arg when drawing bmp with the draw func
|
||||
|
||||
const unsigned char splash_bmp[] = {
|
||||
0x42, 0x4d, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x00,
|
||||
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xe6, 0x00, 0x00, 0x00, 0x32, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x9c, 0x0e,
|
||||
0x00, 0x00, 0x12, 0x0b, 0x00, 0x00, 0x12, 0x0b, 0x00, 0x00, 0x0f, 0x00,
|
||||
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x19, 0xf6, 0x00, 0x00, 0x14,
|
||||
0xc9, 0x00, 0x00, 0x0c, 0x78, 0x00, 0x16, 0x18, 0x29, 0x00, 0xfd, 0xfd,
|
||||
0xfd, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0xe7, 0xe7, 0xe7, 0x00, 0xda, 0xda,
|
||||
0xda, 0x00, 0xcd, 0xcd, 0xcd, 0x00, 0xc7, 0xc7, 0xc7, 0x00, 0xba, 0xba,
|
||||
0xba, 0x00, 0xad, 0xad, 0xad, 0x00, 0x96, 0x96, 0x96, 0x00, 0x5c, 0x5c,
|
||||
0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
|
||||
0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0x00, 0x08,
|
||||
0x33, 0x44, 0x44, 0x47, 0x0a, 0x33, 0x00, 0x06, 0xd5, 0x44, 0x7d, 0x00,
|
||||
0x0a, 0x33, 0x00, 0x0a, 0x44, 0x44, 0x48, 0x33, 0x3a, 0x00, 0x06, 0x44,
|
||||
0x02, 0xd3, 0x12, 0x33, 0x00, 0x08, 0xa4, 0x44, 0x45, 0x9d, 0x08, 0x33,
|
||||
0x00, 0x08, 0xc4, 0x44, 0x45, 0xc3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x44,
|
||||
0x44, 0x6d, 0x16, 0x33, 0x00, 0x08, 0x34, 0x44, 0x44, 0x6d, 0x08, 0x33,
|
||||
0x02, 0xd5, 0x1a, 0x44, 0x00, 0x0c, 0x45, 0xc3, 0x33, 0x44, 0x44, 0x47,
|
||||
0x0a, 0x33, 0x00, 0x06, 0xd5, 0x44, 0x7d, 0x00, 0x0a, 0x33, 0x00, 0x08,
|
||||
0x44, 0x44, 0x48, 0x33, 0x00, 0x00, 0x00, 0x0a, 0x33, 0x49, 0x99, 0x95,
|
||||
0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a, 0xd4, 0x46, 0x89, 0x98, 0xb3, 0x00,
|
||||
0x08, 0x33, 0x00, 0x12, 0x49, 0x99, 0x94, 0x33, 0x3d, 0x79, 0x99, 0x99,
|
||||
0x73, 0x00, 0x10, 0x33, 0x00, 0x0a, 0x3d, 0x47, 0x99, 0x95, 0xa3, 0x00,
|
||||
0x08, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c,
|
||||
0x06, 0x33, 0x00, 0x06, 0xb4, 0x56, 0x89, 0x00, 0x18, 0x99, 0x00, 0x0e,
|
||||
0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a,
|
||||
0xd4, 0x46, 0x89, 0x98, 0xb3, 0x00, 0x08, 0x33, 0x00, 0x08, 0x49, 0x99,
|
||||
0x94, 0x33, 0x00, 0x00, 0x00, 0x0a, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00,
|
||||
0x06, 0x33, 0x02, 0x55, 0x06, 0x99, 0x02, 0x9b, 0x08, 0x33, 0x00, 0x12,
|
||||
0x49, 0x99, 0x94, 0x33, 0x33, 0xb9, 0x99, 0x99, 0x8d, 0x00, 0x10, 0x33,
|
||||
0x00, 0x0a, 0x37, 0x69, 0x99, 0x74, 0xd3, 0x00, 0x08, 0x33, 0x00, 0x08,
|
||||
0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c,
|
||||
0x16, 0x33, 0x00, 0x10, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x33, 0x3a, 0x47,
|
||||
0x1c, 0x99, 0x00, 0x0e, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x00,
|
||||
0x06, 0x33, 0x02, 0x55, 0x06, 0x99, 0x02, 0x9b, 0x08, 0x33, 0x00, 0x08,
|
||||
0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95,
|
||||
0xd3, 0x33, 0x33, 0x3a, 0x48, 0x00, 0x06, 0x99, 0x02, 0x98, 0x02, 0xd3,
|
||||
0x06, 0x33, 0x00, 0x12, 0x49, 0x99, 0x94, 0x33, 0x33, 0xd7, 0x99, 0x99,
|
||||
0x98, 0x00, 0x10, 0x33, 0x00, 0x08, 0xd4, 0x89, 0x99, 0x5a, 0x0a, 0x33,
|
||||
0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99,
|
||||
0x99, 0x7c, 0x16, 0x33, 0x00, 0x10, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x33,
|
||||
0xb4, 0x79, 0x1c, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95,
|
||||
0xd3, 0x33, 0x33, 0x3a, 0x48, 0x00, 0x06, 0x99, 0x02, 0x98, 0x02, 0xd3,
|
||||
0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x12,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x33, 0xd4, 0x79, 0x00, 0x08, 0x99,
|
||||
0x02, 0x73, 0x06, 0x33, 0x00, 0x0c, 0x49, 0x99, 0x94, 0x33, 0x33, 0x3b,
|
||||
0x06, 0x99, 0x02, 0xc3, 0x0e, 0x33, 0x00, 0x08, 0x56, 0x99, 0x97, 0x4d,
|
||||
0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
|
||||
0x33, 0x3d, 0x47, 0x00, 0x1e, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49,
|
||||
0x99, 0x95, 0xd3, 0x33, 0x33, 0xd4, 0x79, 0x00, 0x08, 0x99, 0x02, 0x73,
|
||||
0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x10,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x33, 0xa5, 0x0a, 0x99, 0x02, 0x8d,
|
||||
0x06, 0x33, 0x00, 0x14, 0x49, 0x99, 0x94, 0x33, 0x33, 0x3d, 0x79, 0x99,
|
||||
0x99, 0x73, 0x0c, 0x33, 0x00, 0x0a, 0x3c, 0x48, 0x99, 0x95, 0xa3, 0x00,
|
||||
0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x97, 0xa3, 0x10, 0x33, 0x00, 0x08,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
|
||||
0x33, 0x37, 0x69, 0x00, 0x1e, 0x99, 0x00, 0x14, 0x97, 0xa3, 0x33, 0x49,
|
||||
0x99, 0x95, 0xd3, 0x33, 0x33, 0xa5, 0x0a, 0x99, 0x02, 0x8d, 0x06, 0x33,
|
||||
0x00, 0x08, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x1c, 0x33, 0x49,
|
||||
0x99, 0x95, 0xd3, 0x33, 0x3d, 0x47, 0x99, 0x97, 0x89, 0x99, 0x99, 0x97,
|
||||
0x06, 0x33, 0x00, 0x06, 0x49, 0x99, 0x94, 0x00, 0x06, 0x33, 0x00, 0x08,
|
||||
0xb9, 0x99, 0x99, 0x9d, 0x0c, 0x33, 0x00, 0x0a, 0x34, 0x69, 0x99, 0x74,
|
||||
0xd3, 0x00, 0x0a, 0x33, 0x00, 0x08, 0xc7, 0x99, 0x98, 0xa3, 0x10, 0x33,
|
||||
0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99,
|
||||
0x99, 0x7c, 0x33, 0x34, 0x89, 0x99, 0x64, 0x00, 0x1c, 0x44, 0x00, 0x1e,
|
||||
0xa3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3d, 0x47, 0x99, 0x97, 0x89,
|
||||
0x99, 0x99, 0x97, 0x00, 0x06, 0x33, 0x00, 0x08, 0x49, 0x99, 0x94, 0x33,
|
||||
0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3c, 0x59,
|
||||
0x99, 0x95, 0xbc, 0x99, 0x99, 0x98, 0xd3, 0x33, 0x33, 0x49, 0x99, 0x94,
|
||||
0x06, 0x33, 0x02, 0xd7, 0x06, 0x99, 0x02, 0x74, 0x0c, 0x44, 0x00, 0x06,
|
||||
0x99, 0x99, 0x4b, 0x00, 0x0c, 0x33, 0x00, 0x08, 0xb7, 0x99, 0x99, 0xa3,
|
||||
0x10, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x16, 0x33, 0x00, 0x12,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x33, 0xd5, 0x99, 0x99, 0x6d, 0x00, 0x20, 0x33,
|
||||
0x00, 0x28, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x3c, 0x59, 0x99, 0x95, 0xbc,
|
||||
0x99, 0x99, 0x98, 0xd3, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00,
|
||||
0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x34, 0x79, 0x99, 0x84,
|
||||
0xdd, 0x89, 0x99, 0x99, 0x83, 0x33, 0x33, 0x49, 0x99, 0x94, 0x06, 0x33,
|
||||
0x02, 0x3b, 0x16, 0x99, 0x02, 0x97, 0x02, 0x43, 0x0a, 0x33, 0x00, 0x0a,
|
||||
0x3d, 0x47, 0x99, 0x99, 0x9d, 0x00, 0x10, 0x33, 0x00, 0x08, 0x34, 0x99,
|
||||
0x99, 0x9d, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6,
|
||||
0x99, 0x99, 0x6d, 0x00, 0x20, 0x33, 0x00, 0x28, 0x49, 0x99, 0x95, 0xd3,
|
||||
0x33, 0x34, 0x79, 0x99, 0x84, 0xdd, 0x89, 0x99, 0x99, 0x83, 0x33, 0x33,
|
||||
0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95,
|
||||
0xd3, 0x33, 0xc4, 0x99, 0x99, 0x67, 0x33, 0x79, 0x99, 0x99, 0x8d, 0x33,
|
||||
0x33, 0x49, 0x99, 0x94, 0x06, 0x33, 0x02, 0x3d, 0x02, 0x79, 0x14, 0x99,
|
||||
0x02, 0x94, 0x02, 0xc3, 0x0a, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x99,
|
||||
0x98, 0x00, 0x10, 0x33, 0x02, 0x34, 0x06, 0x99, 0x02, 0x74, 0x16, 0x44,
|
||||
0x00, 0x10, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x9d, 0x20, 0x33,
|
||||
0x00, 0x28, 0x49, 0x99, 0x95, 0xd3, 0x33, 0xc4, 0x99, 0x99, 0x67, 0x33,
|
||||
0x79, 0x99, 0x99, 0x8d, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00,
|
||||
0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x57, 0x99, 0x99, 0x4d,
|
||||
0x33, 0xd8, 0x99, 0x99, 0x98, 0x33, 0x33, 0x49, 0x99, 0x94, 0x08, 0x33,
|
||||
0x02, 0xc9, 0x14, 0x99, 0x02, 0x74, 0x0c, 0x33, 0x02, 0x84, 0x02, 0x89,
|
||||
0x06, 0x99, 0x02, 0xa3, 0x0e, 0x33, 0x02, 0x34, 0x22, 0x99, 0x00, 0x06,
|
||||
0x7c, 0x33, 0xd6, 0x00, 0x06, 0x99, 0x02, 0x64, 0x18, 0x44, 0x00, 0x2e,
|
||||
0x45, 0xc3, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x33, 0x57, 0x99, 0x99, 0x4d,
|
||||
0x33, 0xd8, 0x99, 0x99, 0x98, 0x33, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x3d, 0x48, 0x99,
|
||||
0x97, 0x43, 0x33, 0x37, 0x99, 0x99, 0x97, 0xd3, 0x33, 0x49, 0x99, 0x94,
|
||||
0x08, 0x33, 0x02, 0x37, 0x14, 0x99, 0x02, 0x4c, 0x0a, 0x33, 0x02, 0x3b,
|
||||
0x02, 0x48, 0x08, 0x99, 0x02, 0x9b, 0x0e, 0x33, 0x02, 0x34, 0x22, 0x99,
|
||||
0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x2e, 0x97, 0xa3,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0x3d, 0x48, 0x99, 0x97, 0x43, 0x33, 0x37,
|
||||
0x99, 0x99, 0x97, 0xd3, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00,
|
||||
0x00, 0x28, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x35, 0x69, 0x99, 0x95, 0xc3,
|
||||
0x33, 0x3c, 0x89, 0x99, 0x99, 0xb3, 0x33, 0x49, 0x99, 0x94, 0x08, 0x33,
|
||||
0x02, 0x3c, 0x12, 0x99, 0x02, 0x97, 0x02, 0x43, 0x0a, 0x33, 0x02, 0xd4,
|
||||
0x02, 0x79, 0x0a, 0x99, 0x02, 0xd3, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
|
||||
0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x2e, 0x97, 0xa3,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0x35, 0x69, 0x99, 0x95, 0xc3, 0x33, 0x3c,
|
||||
0x89, 0x99, 0x99, 0xb3, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00,
|
||||
0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0xd3, 0xd4, 0x89, 0x99, 0x74, 0x00,
|
||||
0x06, 0x33, 0x00, 0x10, 0x79, 0x99, 0x99, 0x7d, 0x33, 0x49, 0x99, 0x94,
|
||||
0x0a, 0x33, 0x00, 0x16, 0x79, 0x99, 0x99, 0x64, 0x44, 0x44, 0x89, 0x99,
|
||||
0x99, 0x94, 0xc3, 0x00, 0x08, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x98,
|
||||
0x59, 0x00, 0x06, 0x99, 0x02, 0x8d, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
|
||||
0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x16, 0x97, 0xa3,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0xd4, 0x89, 0x99, 0x74, 0x00, 0x06, 0x33,
|
||||
0x00, 0x12, 0x79, 0x99, 0x99, 0x7d, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0xd3, 0x76, 0x99, 0x99,
|
||||
0x6a, 0x00, 0x06, 0x33, 0x00, 0x10, 0xa9, 0x99, 0x99, 0x9b, 0x33, 0x49,
|
||||
0x99, 0x94, 0x0a, 0x33, 0x00, 0x14, 0xc9, 0x99, 0x99, 0x9c, 0x33, 0x33,
|
||||
0x3b, 0x99, 0x99, 0x74, 0x0a, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x95,
|
||||
0x7d, 0x00, 0x06, 0x99, 0x02, 0x9a, 0x0c, 0x33, 0x02, 0x34, 0x22, 0x99,
|
||||
0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00, 0x20, 0x99, 0x00, 0x16, 0x97, 0xa3,
|
||||
0x33, 0x49, 0x99, 0x95, 0xd3, 0x76, 0x99, 0x99, 0x6a, 0x00, 0x06, 0x33,
|
||||
0x00, 0x12, 0xa9, 0x99, 0x99, 0x9b, 0x33, 0x49, 0x99, 0x94, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x95, 0x3d, 0x48, 0x99, 0x98,
|
||||
0x4d, 0x00, 0x06, 0x33, 0x00, 0x10, 0xd7, 0x99, 0x99, 0x97, 0xd3, 0x49,
|
||||
0x99, 0x94, 0x0a, 0x33, 0x00, 0x14, 0x37, 0x99, 0x99, 0x97, 0x33, 0x33,
|
||||
0xc4, 0x89, 0x99, 0x4c, 0x0a, 0x33, 0x00, 0x0c, 0x84, 0x89, 0x99, 0x64,
|
||||
0x33, 0xd9, 0x06, 0x99, 0x02, 0xa3, 0x0a, 0x33, 0x00, 0x08, 0x34, 0x99,
|
||||
0x99, 0x64, 0x16, 0x44, 0x06, 0x99, 0x00, 0x06, 0x7c, 0x33, 0xd6, 0x00,
|
||||
0x20, 0x99, 0x00, 0x16, 0x97, 0xa3, 0x33, 0x49, 0x99, 0x95, 0x3d, 0x48,
|
||||
0x99, 0x98, 0x4d, 0x00, 0x06, 0x33, 0x00, 0x12, 0xd7, 0x99, 0x99, 0x97,
|
||||
0xd3, 0x49, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49,
|
||||
0x99, 0x96, 0x3b, 0x69, 0x99, 0x96, 0x53, 0x00, 0x06, 0x33, 0x02, 0x38,
|
||||
0x06, 0x99, 0x00, 0x08, 0xc3, 0x49, 0x99, 0x94, 0x0a, 0x33, 0x02, 0x3c,
|
||||
0x06, 0x99, 0x00, 0x0c, 0xb3, 0x33, 0x46, 0x99, 0x96, 0x43, 0x08, 0x33,
|
||||
0x00, 0x0e, 0x3b, 0x48, 0x99, 0x97, 0x4d, 0x33, 0x3d, 0x00, 0x06, 0x99,
|
||||
0x02, 0x9c, 0x0a, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33,
|
||||
0x00, 0x12, 0x39, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x54, 0x00,
|
||||
0x1c, 0x44, 0x00, 0x14, 0xa3, 0x33, 0x49, 0x99, 0x96, 0x3b, 0x69, 0x99,
|
||||
0x96, 0x53, 0x06, 0x33, 0x02, 0x38, 0x06, 0x99, 0x00, 0x0a, 0xc3, 0x49,
|
||||
0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12, 0x33, 0x49, 0x99, 0x98,
|
||||
0xd5, 0x79, 0x99, 0x94, 0xd3, 0x00, 0x06, 0x33, 0x00, 0x10, 0x3d, 0x89,
|
||||
0x99, 0x99, 0x73, 0x49, 0x99, 0x94, 0x0c, 0x33, 0x00, 0x12, 0x79, 0x99,
|
||||
0x99, 0x8d, 0x3a, 0x59, 0x99, 0x84, 0xd3, 0x00, 0x08, 0x33, 0x00, 0x10,
|
||||
0xd4, 0x79, 0x99, 0x84, 0xb3, 0x33, 0x33, 0xb9, 0x06, 0x99, 0x02, 0xd3,
|
||||
0x08, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x12,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6, 0x99, 0x99, 0x5d, 0x00, 0x20, 0x33,
|
||||
0x00, 0x10, 0x49, 0x99, 0x98, 0xd5, 0x79, 0x99, 0x94, 0xd3, 0x06, 0x33,
|
||||
0x00, 0x12, 0x3d, 0x89, 0x99, 0x99, 0x73, 0x49, 0x99, 0x94, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x10, 0x33, 0x49, 0x99, 0x99, 0xa6, 0x99, 0x99, 0x74,
|
||||
0x0a, 0x33, 0x00, 0x0e, 0x79, 0x99, 0x99, 0x9c, 0x59, 0x99, 0x94, 0x00,
|
||||
0x0c, 0x33, 0x00, 0x10, 0xc9, 0x99, 0x99, 0x9a, 0xd4, 0x79, 0x99, 0x65,
|
||||
0x08, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x98, 0x48, 0x00, 0x06, 0x33,
|
||||
0x02, 0x3a, 0x06, 0x99, 0x02, 0x83, 0x08, 0x33, 0x00, 0x08, 0x34, 0x99,
|
||||
0x99, 0x6d, 0x16, 0x33, 0x00, 0x12, 0x34, 0x99, 0x99, 0x7c, 0x33, 0xd6,
|
||||
0x99, 0x99, 0x93, 0x00, 0x20, 0x33, 0x00, 0x0e, 0x49, 0x99, 0x99, 0xa6,
|
||||
0x99, 0x99, 0x74, 0x00, 0x0a, 0x33, 0x00, 0x10, 0x79, 0x99, 0x99, 0x9c,
|
||||
0x59, 0x99, 0x94, 0x33, 0x00, 0x00, 0x00, 0x10, 0x33, 0x49, 0x99, 0x99,
|
||||
0x98, 0x99, 0x99, 0x5c, 0x0a, 0x33, 0x02, 0xd8, 0x06, 0x99, 0x00, 0x06,
|
||||
0x79, 0x99, 0x84, 0x00, 0x0c, 0x33, 0x02, 0x37, 0x06, 0x99, 0x00, 0x08,
|
||||
0x86, 0x99, 0x98, 0x4d, 0x08, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x95,
|
||||
0x53, 0x00, 0x08, 0x33, 0x00, 0x08, 0x89, 0x99, 0x99, 0x9a, 0x08, 0x33,
|
||||
0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x0c, 0x34, 0x99,
|
||||
0x99, 0x7c, 0x33, 0x36, 0x06, 0x99, 0x02, 0x54, 0x18, 0x44, 0x00, 0x14,
|
||||
0x45, 0xc3, 0x33, 0x49, 0x99, 0x99, 0x98, 0x99, 0x99, 0x5c, 0x0a, 0x33,
|
||||
0x02, 0xd8, 0x06, 0x99, 0x00, 0x08, 0x79, 0x99, 0x84, 0x33, 0x00, 0x00,
|
||||
0x02, 0x33, 0x02, 0x59, 0x08, 0x99, 0x02, 0x97, 0x02, 0x4d, 0x0a, 0x33,
|
||||
0x02, 0x36, 0x0a, 0x99, 0x02, 0x75, 0x0c, 0x33, 0x02, 0x3c, 0x0a, 0x99,
|
||||
0x02, 0x96, 0x02, 0x53, 0x08, 0x33, 0x00, 0x0a, 0x84, 0x89, 0x99, 0x64,
|
||||
0xd3, 0x00, 0x08, 0x33, 0x02, 0xd9, 0x06, 0x99, 0x02, 0xb3, 0x06, 0x33,
|
||||
0x00, 0x08, 0x34, 0x99, 0x99, 0x6d, 0x16, 0x33, 0x00, 0x0c, 0x34, 0x99,
|
||||
0x99, 0x7c, 0x33, 0x36, 0x20, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0x59,
|
||||
0x08, 0x99, 0x02, 0x97, 0x02, 0x4d, 0x0a, 0x33, 0x02, 0x36, 0x0a, 0x99,
|
||||
0x02, 0x75, 0x02, 0x33, 0x00, 0x00, 0x02, 0x33, 0x02, 0xa8, 0x08, 0x99,
|
||||
0x02, 0x95, 0x02, 0xa3, 0x0a, 0x33, 0x02, 0x3c, 0x0a, 0x99, 0x02, 0x5b,
|
||||
0x0e, 0x33, 0x02, 0x89, 0x08, 0x99, 0x02, 0x84, 0x02, 0xd3, 0x06, 0x33,
|
||||
0x00, 0x0a, 0x3b, 0x48, 0x99, 0x97, 0x4c, 0x00, 0x0a, 0x33, 0x02, 0x3d,
|
||||
0x06, 0x99, 0x02, 0x9d, 0x06, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x6d,
|
||||
0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c, 0x33, 0x3d, 0x89, 0x00,
|
||||
0x1e, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0xa8, 0x08, 0x99, 0x02, 0x95,
|
||||
0x02, 0xa3, 0x0a, 0x33, 0x02, 0x3c, 0x0a, 0x99, 0x02, 0x5b, 0x02, 0x33,
|
||||
0x00, 0x00, 0x02, 0x33, 0x02, 0xd7, 0x08, 0x99, 0x02, 0x64, 0x0e, 0x33,
|
||||
0x02, 0x79, 0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd8,
|
||||
0x06, 0x99, 0x02, 0x98, 0x02, 0x48, 0x08, 0x33, 0x00, 0x0a, 0xd4, 0x79,
|
||||
0x99, 0x84, 0xb3, 0x00, 0x0c, 0x33, 0x00, 0x16, 0xb9, 0x99, 0x99, 0x98,
|
||||
0xd3, 0x33, 0x33, 0x34, 0x99, 0x99, 0x6d, 0x00, 0x16, 0x33, 0x00, 0x0c,
|
||||
0x34, 0x99, 0x99, 0x7c, 0x33, 0x33, 0x20, 0x99, 0x00, 0x08, 0x97, 0xa3,
|
||||
0x33, 0xd7, 0x08, 0x99, 0x02, 0x64, 0x0e, 0x33, 0x02, 0x79, 0x06, 0x99,
|
||||
0x00, 0x06, 0x96, 0x4d, 0x33, 0x00, 0x00, 0x00, 0x02, 0x33, 0x02, 0x3c,
|
||||
0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd9, 0x06, 0x99,
|
||||
0x02, 0x74, 0x02, 0xc3, 0x0e, 0x33, 0x02, 0x3d, 0x06, 0x99, 0x02, 0x85,
|
||||
0x02, 0x53, 0x06, 0x33, 0x00, 0x0a, 0x3d, 0x46, 0x99, 0x99, 0x57, 0x00,
|
||||
0x0e, 0x33, 0x02, 0x39, 0x06, 0x99, 0x00, 0x0e, 0xa3, 0x33, 0x33, 0x34,
|
||||
0x99, 0x99, 0x6d, 0x00, 0x16, 0x33, 0x00, 0x0e, 0x34, 0x99, 0x99, 0x7c,
|
||||
0x33, 0x33, 0x3a, 0x00, 0x1e, 0x99, 0x00, 0x08, 0x97, 0xa3, 0x33, 0x3c,
|
||||
0x06, 0x99, 0x02, 0x96, 0x02, 0x4d, 0x0e, 0x33, 0x02, 0xd9, 0x06, 0x99,
|
||||
0x00, 0x06, 0x74, 0xc3, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x33, 0x33,
|
||||
0xc8, 0x99, 0x96, 0x44, 0xd3, 0x00, 0x0e, 0x33, 0x00, 0x0a, 0x3d, 0x79,
|
||||
0x99, 0x76, 0x4b, 0x00, 0x12, 0x33, 0x00, 0x0a, 0xc7, 0x99, 0x96, 0x45,
|
||||
0xd3, 0x00, 0x06, 0x33, 0x00, 0x0a, 0x35, 0x59, 0x99, 0x96, 0x43, 0x00,
|
||||
0x10, 0x33, 0x00, 0x14, 0x89, 0x99, 0x99, 0x9a, 0x33, 0x33, 0x34, 0x99,
|
||||
0x99, 0x6d, 0x16, 0x33, 0x00, 0x08, 0x34, 0x99, 0x99, 0x7c, 0x06, 0x33,
|
||||
0x02, 0xa6, 0x1c, 0x99, 0x00, 0x12, 0x97, 0xa3, 0x33, 0x33, 0xc8, 0x99,
|
||||
0x96, 0x44, 0xd3, 0x00, 0x0e, 0x33, 0x00, 0x0e, 0x3d, 0x79, 0x99, 0x76,
|
||||
0x4b, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x33, 0x33, 0x3d, 0x84,
|
||||
0x44, 0xc3, 0x12, 0x33, 0x00, 0x08, 0xdc, 0x44, 0x4a, 0xd3, 0x12, 0x33,
|
||||
0x00, 0x08, 0x3d, 0xa4, 0x44, 0xd3, 0x08, 0x33, 0x02, 0xc8, 0x06, 0x44,
|
||||
0x02, 0xd3, 0x10, 0x33, 0x02, 0xd4, 0x06, 0x44, 0x00, 0x0c, 0xa3, 0x33,
|
||||
0x38, 0x44, 0x44, 0x4d, 0x16, 0x33, 0x00, 0x08, 0x38, 0x44, 0x44, 0x4c,
|
||||
0x06, 0x33, 0x02, 0x3d, 0x02, 0xc4, 0x1c, 0x44, 0x00, 0x0e, 0xa3, 0x33,
|
||||
0x33, 0x3d, 0x84, 0x44, 0xc3, 0x00, 0x12, 0x33, 0x00, 0x0c, 0xdc, 0x44,
|
||||
0x4a, 0xd3, 0x33, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
|
||||
0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
|
||||
0x00, 0x00, 0x00, 0x1e, 0x33, 0x20, 0x00, 0x23, 0x30, 0x00, 0x13, 0x31,
|
||||
0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x26,
|
||||
0x13, 0x31, 0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x00, 0x00, 0x13, 0x33,
|
||||
0x00, 0x00, 0x13, 0x32, 0x00, 0x00, 0x23, 0x00, 0x24, 0x33, 0x00, 0x06,
|
||||
0x32, 0x00, 0x02, 0x00, 0x08, 0x33, 0x02, 0x32, 0x06, 0x00, 0x00, 0x30,
|
||||
0x01, 0x33, 0x31, 0x00, 0x02, 0x33, 0x00, 0x01, 0x33, 0x33, 0x32, 0x00,
|
||||
0x02, 0x33, 0x33, 0x30, 0x00, 0x13, 0x32, 0x00, 0x02, 0x33, 0x00, 0x01,
|
||||
0x08, 0x33, 0x00, 0x10, 0x30, 0x00, 0x13, 0x31, 0x00, 0x02, 0x33, 0x30,
|
||||
0x08, 0x00, 0x00, 0x10, 0x23, 0x30, 0x00, 0x13, 0x31, 0x00, 0x02, 0x33,
|
||||
0x00, 0x00, 0x00, 0x20, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x10, 0x10, 0x06, 0x00, 0x00, 0x26,
|
||||
0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x03, 0x33,
|
||||
0x01, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x00, 0x24, 0x33, 0x00, 0x06,
|
||||
0x32, 0x01, 0x11, 0x00, 0x08, 0x33, 0x00, 0x38, 0x10, 0x10, 0x00, 0x11,
|
||||
0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x33, 0x32, 0x01,
|
||||
0x11, 0x33, 0x33, 0x30, 0x11, 0x03, 0x32, 0x01, 0x11, 0x33, 0x01, 0x10,
|
||||
0x08, 0x33, 0x00, 0x12, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00,
|
||||
0x10, 0x00, 0x06, 0x00, 0x00, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31, 0x11,
|
||||
0x02, 0x33, 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11,
|
||||
0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00,
|
||||
0x06, 0x33, 0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01,
|
||||
0x11, 0x13, 0x33, 0x01, 0x10, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33,
|
||||
0x00, 0x06, 0x32, 0x01, 0x11, 0x00, 0x08, 0x33, 0x00, 0x38, 0x11, 0x10,
|
||||
0x23, 0x31, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x33,
|
||||
0x32, 0x01, 0x11, 0x33, 0x33, 0x30, 0x11, 0x03, 0x32, 0x01, 0x11, 0x33,
|
||||
0x01, 0x10, 0x08, 0x33, 0x00, 0x12, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02,
|
||||
0x33, 0x01, 0x10, 0x00, 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x23,
|
||||
0x30, 0x11, 0x03, 0x31, 0x11, 0x12, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10,
|
||||
0x23, 0x00, 0x06, 0x33, 0x00, 0x28, 0x31, 0x11, 0x12, 0x33, 0x01, 0x00,
|
||||
0x33, 0x01, 0x11, 0x11, 0x00, 0x01, 0x12, 0x33, 0x32, 0x01, 0x11, 0x11,
|
||||
0x00, 0x23, 0x20, 0x33, 0x00, 0x06, 0x32, 0x01, 0x12, 0x00, 0x08, 0x33,
|
||||
0x00, 0x38, 0x11, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31, 0x11, 0x12, 0x33,
|
||||
0x01, 0x00, 0x33, 0x33, 0x32, 0x01, 0x11, 0x33, 0x33, 0x30, 0x11, 0x13,
|
||||
0x32, 0x01, 0x11, 0x33, 0x01, 0x11, 0x08, 0x33, 0x00, 0x12, 0x30, 0x11,
|
||||
0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x00, 0x08, 0x33, 0x00, 0x0e,
|
||||
0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x22,
|
||||
0x33, 0x20, 0x11, 0x11, 0x00, 0x11, 0x03, 0x31, 0x11, 0x11, 0x00, 0x01,
|
||||
0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33, 0x00, 0x10, 0x31, 0x11,
|
||||
0x11, 0x00, 0x01, 0x13, 0x33, 0x01, 0x08, 0x11, 0x00, 0x10, 0x12, 0x33,
|
||||
0x32, 0x01, 0x11, 0x11, 0x10, 0x23, 0x20, 0x33, 0x00, 0x60, 0x32, 0x01,
|
||||
0x11, 0x10, 0x00, 0x03, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x12, 0x22, 0x01, 0x02, 0x33, 0x33, 0x32, 0x01, 0x11, 0x33, 0x33,
|
||||
0x30, 0x11, 0x12, 0x21, 0x01, 0x11, 0x33, 0x01, 0x11, 0x22, 0x22, 0x23,
|
||||
0x33, 0x30, 0x11, 0x12, 0x21, 0x11, 0x02, 0x33, 0x01, 0x10, 0x08, 0x33,
|
||||
0x00, 0x0e, 0x30, 0x11, 0x12, 0x21, 0x10, 0x13, 0x33, 0x00, 0x00, 0x00,
|
||||
0x00, 0x22, 0x33, 0x20, 0x11, 0x00, 0x11, 0x11, 0x03, 0x31, 0x11, 0x00,
|
||||
0x01, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33, 0x00, 0x28,
|
||||
0x31, 0x11, 0x00, 0x01, 0x11, 0x13, 0x33, 0x01, 0x11, 0x00, 0x01, 0x11,
|
||||
0x12, 0x33, 0x32, 0x01, 0x11, 0x00, 0x00, 0x23, 0x20, 0x33, 0x00, 0x60,
|
||||
0x32, 0x01, 0x10, 0x01, 0x11, 0x12, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11,
|
||||
0x03, 0x31, 0x11, 0x10, 0x10, 0x11, 0x13, 0x33, 0x33, 0x32, 0x01, 0x11,
|
||||
0x33, 0x33, 0x30, 0x11, 0x10, 0x10, 0x11, 0x11, 0x33, 0x01, 0x11, 0x01,
|
||||
0x00, 0x01, 0x23, 0x30, 0x11, 0x00, 0x10, 0x11, 0x02, 0x33, 0x01, 0x10,
|
||||
0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x00, 0x10, 0x11, 0x23, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x31, 0x11, 0x03, 0x31,
|
||||
0x11, 0x02, 0x33, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33,
|
||||
0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x11, 0x10, 0x33, 0x01, 0x11, 0x03,
|
||||
0x33, 0x11, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33, 0x00, 0x60,
|
||||
0x32, 0x01, 0x11, 0x33, 0x11, 0x10, 0x33, 0x11, 0x10, 0x23, 0x30, 0x11,
|
||||
0x03, 0x31, 0x11, 0x01, 0x22, 0x11, 0x12, 0x33, 0x33, 0x32, 0x01, 0x11,
|
||||
0x33, 0x33, 0x30, 0x11, 0x02, 0x22, 0x11, 0x11, 0x33, 0x01, 0x10, 0x22,
|
||||
0x21, 0x11, 0x13, 0x30, 0x11, 0x02, 0x21, 0x11, 0x02, 0x33, 0x01, 0x10,
|
||||
0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x02, 0x21, 0x11, 0x23, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x11, 0x10, 0x23, 0x00, 0x06, 0x33,
|
||||
0x00, 0x24, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x13,
|
||||
0x33, 0x01, 0x11, 0x13, 0x32, 0x01, 0x11, 0x23, 0x24, 0x33, 0x00, 0x60,
|
||||
0x32, 0x01, 0x11, 0x33, 0x01, 0x10, 0x33, 0x11, 0x11, 0x23, 0x30, 0x11,
|
||||
0x03, 0x31, 0x11, 0x12, 0x33, 0x11, 0x10, 0x33, 0x33, 0x32, 0x01, 0x11,
|
||||
0x33, 0x33, 0x30, 0x11, 0x13, 0x32, 0x11, 0x11, 0x33, 0x01, 0x11, 0x33,
|
||||
0x21, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10,
|
||||
0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00,
|
||||
0x00, 0x00, 0x00, 0x22, 0x33, 0x20, 0x11, 0x13, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x12, 0x33, 0x01, 0x00, 0x33, 0x11, 0x11, 0x23, 0x00, 0x06, 0x33,
|
||||
0x00, 0x2c, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01, 0x11, 0x11,
|
||||
0x00, 0x01, 0x10, 0x13, 0x32, 0x01, 0x11, 0x11, 0x00, 0x00, 0x01, 0x23,
|
||||
0x1c, 0x33, 0x00, 0x60, 0x32, 0x01, 0x12, 0x33, 0x01, 0x00, 0x33, 0x11,
|
||||
0x11, 0x22, 0x20, 0x11, 0x03, 0x31, 0x11, 0x12, 0x22, 0x01, 0x10, 0x33,
|
||||
0x33, 0x32, 0x01, 0x12, 0x33, 0x33, 0x30, 0x11, 0x12, 0x21, 0x01, 0x11,
|
||||
0x33, 0x01, 0x11, 0x22, 0x10, 0x11, 0x13, 0x30, 0x11, 0x12, 0x21, 0x11,
|
||||
0x02, 0x33, 0x01, 0x11, 0x08, 0x33, 0x00, 0x0e, 0x30, 0x11, 0x03, 0x31,
|
||||
0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x38, 0x33, 0x20, 0x11, 0x13,
|
||||
0x30, 0x11, 0x03, 0x32, 0x11, 0x11, 0x00, 0x01, 0x02, 0x33, 0x21, 0x11,
|
||||
0x10, 0x00, 0x00, 0x13, 0x31, 0x11, 0x02, 0x33, 0x01, 0x10, 0x33, 0x01,
|
||||
0x08, 0x11, 0x00, 0x08, 0x12, 0x33, 0x33, 0x21, 0x08, 0x11, 0x02, 0x10,
|
||||
0x02, 0x23, 0x1c, 0x33, 0x00, 0x0e, 0x32, 0x01, 0x11, 0x10, 0x01, 0x02,
|
||||
0x33, 0x00, 0x06, 0x11, 0x00, 0x62, 0x01, 0x10, 0x03, 0x31, 0x11, 0x11,
|
||||
0x10, 0x11, 0x00, 0x33, 0x10, 0x00, 0x01, 0x11, 0x10, 0x13, 0x30, 0x11,
|
||||
0x11, 0x10, 0x11, 0x01, 0x33, 0x01, 0x11, 0x11, 0x01, 0x10, 0x13, 0x30,
|
||||
0x11, 0x11, 0x00, 0x10, 0x02, 0x33, 0x01, 0x11, 0x10, 0x00, 0x00, 0x23,
|
||||
0x30, 0x11, 0x03, 0x31, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x33, 0x20, 0x00, 0x13, 0x31, 0x00, 0x03, 0x33, 0x20, 0x00, 0x06, 0x00,
|
||||
0x00, 0x06, 0x13, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x12, 0x03, 0x32,
|
||||
0x00, 0x02, 0x33, 0x10, 0x00, 0x33, 0x10, 0x00, 0x08, 0x00, 0x00, 0x08,
|
||||
0x02, 0x33, 0x33, 0x30, 0x0a, 0x00, 0x02, 0x23, 0x1c, 0x33, 0x02, 0x32,
|
||||
0x08, 0x00, 0x00, 0x06, 0x03, 0x33, 0x32, 0x00, 0x06, 0x00, 0x00, 0x06,
|
||||
0x01, 0x33, 0x32, 0x00, 0x08, 0x00, 0x00, 0x06, 0x13, 0x33, 0x20, 0x00,
|
||||
0x08, 0x00, 0x00, 0x06, 0x03, 0x33, 0x10, 0x00, 0x06, 0x00, 0x00, 0x06,
|
||||
0x23, 0x33, 0x10, 0x00, 0x06, 0x00, 0x00, 0x06, 0x02, 0x33, 0x33, 0x00,
|
||||
0x08, 0x00, 0x00, 0x06, 0x23, 0x33, 0x30, 0x00, 0x08, 0x00, 0x00, 0x10,
|
||||
0x23, 0x31, 0x00, 0x03, 0x32, 0x00, 0x02, 0x33, 0x00, 0x00, 0xe6, 0x33,
|
||||
0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33, 0x00, 0x00, 0xe6, 0x33,
|
||||
0x00, 0x01};
|
||||
unsigned int splash_new__new_t_len = 3854;
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
|
||||
file_path = macaddress_dir / u"macaddress.db";
|
||||
index_item_length = 7;
|
||||
record_length = 64;
|
||||
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -60,6 +61,12 @@ class database {
|
||||
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
struct MacAddressDBRecord {
|
||||
char vendor_name[64]; // vendor/manufacturer name
|
||||
};
|
||||
|
||||
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path = ""; // path including filename
|
||||
int index_item_length = 0; // length of index item
|
||||
@@ -77,4 +84,4 @@ class database {
|
||||
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
#endif /*__DATABASE_H__*/
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "de_bruijn.hpp"
|
||||
|
||||
/* How this works:
|
||||
* B(2,4) means:
|
||||
* Alphabet size = 2 (binary)
|
||||
* Code length = 4
|
||||
* The length of the bitstream is (2 ^ 4) + (4 - 1) = 19 bits
|
||||
* The primitive polynomials come from the de_bruijn_polys[] array (one for each code length)
|
||||
* The shift register is init with 1 and shifted left each step
|
||||
* The polynomial is kept on the right, and used as a AND mask applied on the corresponding shift register bits
|
||||
* The resulting bits are XORed together to produce the new bit pushed in the shift register
|
||||
*
|
||||
* 0001 (init)
|
||||
* AND 1001 (polynomial)
|
||||
* 0001 XOR'd -> 1
|
||||
*
|
||||
* 00011 (shift left)
|
||||
* AND 1001
|
||||
* 0001 XOR'd -> 1
|
||||
*
|
||||
* 000111 (shift left)
|
||||
* AND 1001
|
||||
* 0001 XOR'd -> 1
|
||||
*
|
||||
* 0001111 (shift left)
|
||||
* AND 1001
|
||||
* 1001 XOR'd -> 0
|
||||
*
|
||||
* 00011110 (shift left)
|
||||
* AND 1001
|
||||
* 1000 XOR'd -> 1
|
||||
* ...
|
||||
* After 16 steps: (0+) 000111101011001000
|
||||
* Each of the 16 possible values appears in the sequence:
|
||||
* -0000-111101011001000 0
|
||||
* 0-0001-11101011001000 1
|
||||
* 0000111101011-0010-00 2
|
||||
* 00-0011-1101011001000 3
|
||||
* 00001111010110-0100-0 4
|
||||
* 00001111-0101-1001000 5
|
||||
* 0000111101-0110-01000 6
|
||||
* 000-0111-101011001000 7
|
||||
* 000011110101100-1000- 8
|
||||
* 000011110101-1001-000 9
|
||||
* 0000111-1010-11001000 10
|
||||
* 000011110-1011-001000 11
|
||||
* 00001111010-1100-1000 12
|
||||
* 000011-1101-011001000 13
|
||||
* 00001-1110-1011001000 14
|
||||
* 0000-1111-01011001000 15
|
||||
*/
|
||||
|
||||
size_t de_bruijn::init(const uint32_t n) {
|
||||
// Cap
|
||||
if ((n < 3) || (n > 16))
|
||||
length = 3;
|
||||
else
|
||||
length = n;
|
||||
|
||||
poly = de_bruijn_polys[length - 3];
|
||||
shift_register = 1;
|
||||
|
||||
return (1U << length) + (length - 1);
|
||||
}
|
||||
|
||||
uint32_t de_bruijn::compute(const uint32_t steps) {
|
||||
uint32_t step, bits, masked;
|
||||
uint8_t new_bit;
|
||||
|
||||
for (step = 0; step < steps; step++) {
|
||||
masked = shift_register & poly;
|
||||
new_bit = 0;
|
||||
for (bits = 0; bits < length; bits++) {
|
||||
new_bit ^= (masked & 1);
|
||||
masked >>= 1;
|
||||
}
|
||||
shift_register <<= 1;
|
||||
shift_register |= new_bit;
|
||||
}
|
||||
|
||||
return shift_register;
|
||||
}
|
||||
+2
-2
@@ -87,10 +87,10 @@ class ACARSAppView : public View {
|
||||
true};
|
||||
|
||||
Console console{
|
||||
{0, 3 * 16, 240, 256}};
|
||||
{0, 3 * 16, screen_width, 256}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 1 * 16}};
|
||||
{screen_width - 2 * 8, 1 * 16}};
|
||||
|
||||
std::unique_ptr<ACARSLogger> logger{};
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ ADSBCallsignView::ADSBCallsignView(
|
||||
nav,
|
||||
callsign,
|
||||
8,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
button_callsign.set_text(s);
|
||||
});
|
||||
|
||||
+1
-1
@@ -218,7 +218,7 @@ class ADSBTxView : public View {
|
||||
void start_tx();
|
||||
void generate_frames();
|
||||
|
||||
Rect view_rect = {0, 7 * 8, 240, 192};
|
||||
Rect view_rect = {0, 7 * 8, screen_width, 192};
|
||||
|
||||
ADSBPositionView view_position{nav_, view_rect};
|
||||
ADSBCallsignView view_callsign{nav_, view_rect};
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ class AFSKRxView : public View {
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
@@ -105,7 +105,7 @@ class AFSKRxView : public View {
|
||||
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, screen_width}};
|
||||
{0, 4 * 16, screen_width, screen_width}};
|
||||
|
||||
void on_data_afsk(const AFSKDataMessage& message);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class AnalogTvView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_tv", app_settings::Mode::RX};
|
||||
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
|
||||
RSSI rssi{
|
||||
|
||||
@@ -86,7 +86,7 @@ class WhipCalcView : public View {
|
||||
{13 * 8, 4 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
Console console{
|
||||
{0, 6 * 16, 240, 160}};
|
||||
{0, 6 * 16, screen_width, 160}};
|
||||
|
||||
Button button_exit{
|
||||
{72, 17 * 16, 96, 32},
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_battleship.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BattleshipView>();
|
||||
}
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Battleship",
|
||||
/*.bitmap_data = */ {
|
||||
// Pirate galleon - 16x16
|
||||
0x80, 0x00, // ........#.......
|
||||
0x80, 0x00, // ........#.......
|
||||
0x80, 0x00, // ........#.......
|
||||
0xC0, 0x01, // .......###......
|
||||
0xE0, 0x03, // ......#####.....
|
||||
0xF0, 0x07, // .....#######....
|
||||
0xF8, 0x0F, // ....#########...
|
||||
0xFC, 0x1F, // ...###########..
|
||||
0xFE, 0x3F, // ..#############.
|
||||
0x00, 0x01, // .......#........
|
||||
0x00, 0x01, // .......#........
|
||||
0x00, 0x01, // .......#........
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFF, 0xFF, // ################
|
||||
0xFC, 0x3F, // ..############..
|
||||
},
|
||||
/*.icon_color = */ ui::Color::blue().v,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_BATTLESHIP_H__
|
||||
#define __UI_BATTLESHIP_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "message.hpp"
|
||||
#include "pocsag.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
|
||||
constexpr uint8_t GRID_SIZE = 10;
|
||||
constexpr uint8_t CELL_SIZE = 24;
|
||||
constexpr uint8_t GRID_OFFSET_X = 0;
|
||||
constexpr uint8_t GRID_OFFSET_Y = 32;
|
||||
|
||||
enum class ShipType : uint8_t {
|
||||
CARRIER = 5,
|
||||
BATTLESHIP = 4,
|
||||
CRUISER = 3,
|
||||
SUBMARINE = 3,
|
||||
DESTROYER = 2
|
||||
};
|
||||
|
||||
enum class GameState : uint8_t {
|
||||
MENU,
|
||||
PLACING_SHIPS,
|
||||
WAITING_FOR_OPPONENT,
|
||||
MY_TURN,
|
||||
OPPONENT_TURN,
|
||||
GAME_OVER
|
||||
};
|
||||
|
||||
enum class CellState : uint8_t {
|
||||
EMPTY,
|
||||
SHIP,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK
|
||||
};
|
||||
|
||||
enum class MessageType : uint8_t {
|
||||
READY,
|
||||
SHOT,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK,
|
||||
WIN
|
||||
};
|
||||
|
||||
struct Ship {
|
||||
ShipType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
bool horizontal;
|
||||
uint8_t hits;
|
||||
bool placed;
|
||||
|
||||
uint8_t size() const {
|
||||
return static_cast<uint8_t>(type);
|
||||
}
|
||||
|
||||
bool is_sunk() const {
|
||||
return hits >= size();
|
||||
}
|
||||
};
|
||||
|
||||
struct GameMessage {
|
||||
MessageType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
};
|
||||
|
||||
class BattleshipView : public View {
|
||||
public:
|
||||
BattleshipView(NavigationView& nav);
|
||||
~BattleshipView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
|
||||
std::string title() const override { return "Battleship"; }
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState rx_radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
TxRadioState tx_radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
// Settings
|
||||
bool sound_enabled{true};
|
||||
bool rf_amp_enabled{false};
|
||||
uint8_t lna_gain{24};
|
||||
uint8_t vga_gain{24};
|
||||
uint8_t tx_gain{35};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"battleship",
|
||||
app_settings::Mode::RX_TX,
|
||||
{{"rx_freq"sv, &rx_frequency},
|
||||
{"tx_freq"sv, &tx_frequency},
|
||||
{"rf_amp"sv, &rf_amp_enabled}}};
|
||||
|
||||
GameState game_state{GameState::MENU};
|
||||
bool is_red_team{false};
|
||||
bool opponent_ready{false};
|
||||
uint8_t wins{0};
|
||||
uint8_t losses{0};
|
||||
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> my_grid{};
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> enemy_grid{};
|
||||
|
||||
std::string current_status{"Choose your team!"};
|
||||
std::string current_score{"W:0 L:0"};
|
||||
|
||||
std::array<Ship, 5> my_ships{};
|
||||
uint8_t current_ship_index{0};
|
||||
bool placing_horizontal{true};
|
||||
uint8_t ships_remaining{5};
|
||||
uint8_t enemy_ships_remaining{5};
|
||||
|
||||
uint8_t cursor_x{0};
|
||||
uint8_t cursor_y{0};
|
||||
uint8_t target_x{0};
|
||||
uint8_t target_y{0};
|
||||
bool touch_enabled{true};
|
||||
|
||||
uint32_t tx_frequency{433920000};
|
||||
uint32_t rx_frequency{433920000};
|
||||
bool is_transmitting{false};
|
||||
|
||||
// POCSAG decoding state
|
||||
pocsag::EccContainer ecc{};
|
||||
pocsag::POCSAGState pocsag_state{&ecc};
|
||||
uint32_t last_address{0};
|
||||
|
||||
// UI Elements - Menu/Settings Screen
|
||||
Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
|
||||
Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
|
||||
|
||||
Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
|
||||
Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
|
||||
ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
|
||||
|
||||
// Radio controls
|
||||
Text label_rf_amp{{17, 90, 35, 16}, "AMP:"};
|
||||
Checkbox checkbox_rf_amp{{55, 90}, 3, "", false};
|
||||
|
||||
Text label_lna{{17, 118, 30, 16}, "LNA:"};
|
||||
NumberField field_lna{{50, 118}, 2, {0, 40}, 8, ' '};
|
||||
|
||||
Text label_vga{{90, 118, 30, 16}, "VGA:"};
|
||||
NumberField field_vga{{125, 118}, 2, {0, 62}, 8, ' '};
|
||||
|
||||
Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
|
||||
NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
|
||||
|
||||
Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
|
||||
Text label_audio{{17, 169, 80, 16}, "AUDIO"};
|
||||
Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
|
||||
Text label_volume{{110, 187, 50, 16}, "Volume:"};
|
||||
AudioVolumeField field_volume{{165, 187}};
|
||||
|
||||
Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
|
||||
Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
|
||||
Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
|
||||
Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
|
||||
|
||||
// In-game UI elements
|
||||
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
|
||||
Text text_status{{10, 16, 220, 16}, ""};
|
||||
Text text_score{{170, 16, 60, 16}, ""};
|
||||
Button button_rotate{{10, 265, 65, 32}, "Rotate"};
|
||||
Button button_place{{82, 265, 65, 32}, "Place"};
|
||||
Button button_fire{{82, 265, 65, 32}, "Fire!"};
|
||||
Button button_menu{{155, 265, 65, 32}, "Menu"};
|
||||
|
||||
// Methods
|
||||
void init_game();
|
||||
void reset_game();
|
||||
void start_team(bool red);
|
||||
void setup_ships();
|
||||
void draw_menu_screen(Painter& painter);
|
||||
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
|
||||
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
|
||||
void draw_ship_preview(Painter& painter);
|
||||
void place_ship();
|
||||
bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
|
||||
void fire_at_position();
|
||||
void process_shot(uint8_t x, uint8_t y);
|
||||
void update_score();
|
||||
bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
|
||||
|
||||
void send_message(const GameMessage& msg);
|
||||
void process_message(const std::string& message);
|
||||
bool parse_coords(const std::string& coords, uint8_t& x, uint8_t& y);
|
||||
void mark_ship_sunk(uint8_t x, uint8_t y, std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid);
|
||||
|
||||
void configure_radio_tx();
|
||||
void configure_radio_rx();
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::POCSAGPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const POCSAGPacketMessage*>(p);
|
||||
on_pocsag_packet(message);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const TXProgressMessage*>(p);
|
||||
on_tx_progress(message->progress, message->done);
|
||||
}};
|
||||
|
||||
void on_pocsag_packet(const POCSAGPacketMessage* message);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_blackjack.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BlackjackView>();
|
||||
}
|
||||
} // namespace ui::external_app::blackjack
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_blackjack.application_information"), used)) application_information_t _application_information_blackjack = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::blackjack::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Blackjack",
|
||||
{
|
||||
// Diamond icon 16x16
|
||||
0x00, 0x00, // ................
|
||||
0x80, 0x01, // .......##.......
|
||||
0xC0, 0x03, // ......####......
|
||||
0xE0, 0x07, // .....######.....
|
||||
0xF0, 0x0F, // ....########....
|
||||
0xF8, 0x1F, // ...##########...
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xF8, 0x1F, // ...##########...
|
||||
0xF0, 0x0F, // ....########....
|
||||
0xE0, 0x07, // .....######.....
|
||||
0xC0, 0x03, // ......####......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x00, 0x00, // ................
|
||||
},
|
||||
ui::Color::red().v, // Red color for diamonds
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#include "ui_blackjack.hpp"
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
|
||||
// Global variables
|
||||
static BlackjackView* current_instance = nullptr;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
static Painter painter;
|
||||
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
void game_timer_check() {
|
||||
if (current_instance) {
|
||||
current_instance->update_game();
|
||||
}
|
||||
}
|
||||
|
||||
BlackjackView::BlackjackView(NavigationView& nav)
|
||||
: nav_{nav}, game_timer{} {
|
||||
add_children({&dummy});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
}
|
||||
|
||||
BlackjackView::~BlackjackView() {
|
||||
current_instance = nullptr;
|
||||
}
|
||||
|
||||
void BlackjackView::on_show() {
|
||||
draw_menu_static();
|
||||
}
|
||||
|
||||
void BlackjackView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
init_deck();
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BlackjackView::clear_screen() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
}
|
||||
|
||||
void BlackjackView::init_deck() {
|
||||
// Initialize deck with card values 0-51
|
||||
// 0-12 = Spades (A-K), 13-25 = Hearts, 26-38 = Diamonds, 39-51 = Clubs
|
||||
for (int i = 0; i < 52; i++) {
|
||||
deck[i] = i;
|
||||
}
|
||||
shuffle_deck();
|
||||
}
|
||||
|
||||
void BlackjackView::shuffle_deck() {
|
||||
// Simple shuffle using random swaps
|
||||
for (int i = 51; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
uint8_t temp = deck[i];
|
||||
deck[i] = deck[j];
|
||||
deck[j] = temp;
|
||||
}
|
||||
deck_position = 0;
|
||||
}
|
||||
|
||||
uint8_t BlackjackView::draw_card() {
|
||||
if (deck_position >= 52) {
|
||||
shuffle_deck();
|
||||
}
|
||||
return deck[deck_position++];
|
||||
}
|
||||
|
||||
int BlackjackView::get_card_value(uint8_t card) {
|
||||
int value = (card % 13) + 1; // 1-13
|
||||
if (value > 10) value = 10; // Face cards worth 10
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t BlackjackView::get_card_suit(uint8_t card) {
|
||||
return card / 13; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
|
||||
}
|
||||
|
||||
std::string BlackjackView::get_card_string(uint8_t card) {
|
||||
int value = (card % 13) + 1;
|
||||
std::string result;
|
||||
|
||||
if (value == 1)
|
||||
result = "A";
|
||||
else if (value == 11)
|
||||
result = "J";
|
||||
else if (value == 12)
|
||||
result = "Q";
|
||||
else if (value == 13)
|
||||
result = "K";
|
||||
else if (value == 10)
|
||||
result = "10"; // Special case for 10
|
||||
else
|
||||
result = std::to_string(value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int BlackjackView::calculate_hand_value(uint8_t* cards, uint8_t count) {
|
||||
int value = 0;
|
||||
int aces = 0;
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
int card_val = get_card_value(cards[i]);
|
||||
if (card_val == 1) {
|
||||
aces++;
|
||||
value += 1;
|
||||
} else {
|
||||
value += card_val;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert aces from 1 to 11 if beneficial
|
||||
while (aces > 0 && value + 10 <= 21) {
|
||||
value += 10;
|
||||
aces--;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void BlackjackView::deal_cards() {
|
||||
// Clear hands
|
||||
player_card_count = 0;
|
||||
dealer_card_count = 0;
|
||||
|
||||
// Deal initial cards
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
|
||||
dealer_hidden = true;
|
||||
game_state = GameState::PLAYING;
|
||||
}
|
||||
|
||||
void BlackjackView::player_hit() {
|
||||
if (player_card_count < MAX_CARDS_IN_HAND) {
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
|
||||
int value = calculate_hand_value(player_cards, player_card_count);
|
||||
if (value > 21) {
|
||||
// Bust!
|
||||
cash = (cash >= bet) ? cash - bet : 0;
|
||||
losses++;
|
||||
game_state = GameState::GAME_OVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::player_stay() {
|
||||
dealer_hidden = false;
|
||||
game_state = GameState::DEALER_TURN;
|
||||
dealer_timer = 0;
|
||||
}
|
||||
|
||||
void BlackjackView::dealer_turn() {
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
|
||||
if (dealer_value < 17 && dealer_card_count < MAX_CARDS_IN_HAND) {
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
} else {
|
||||
check_game_over();
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::check_game_over() {
|
||||
int player_value = calculate_hand_value(player_cards, player_card_count);
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
|
||||
if (player_value > 21) {
|
||||
// Player bust (already handled)
|
||||
} else if (dealer_value > 21) {
|
||||
// Dealer bust - player wins
|
||||
cash += bet;
|
||||
wins++;
|
||||
} else if (player_value > dealer_value) {
|
||||
// Player wins
|
||||
cash += bet;
|
||||
wins++;
|
||||
} else if (player_value < dealer_value) {
|
||||
// Dealer wins
|
||||
cash = (cash >= bet) ? cash - bet : 0;
|
||||
losses++;
|
||||
}
|
||||
// Else it's a tie - no money changes hands
|
||||
|
||||
if (cash > high_score) {
|
||||
high_score = cash;
|
||||
}
|
||||
|
||||
game_state = GameState::GAME_OVER;
|
||||
}
|
||||
|
||||
void BlackjackView::update_game() {
|
||||
switch (game_state) {
|
||||
case GameState::MENU:
|
||||
draw_menu();
|
||||
break;
|
||||
|
||||
case GameState::BETTING:
|
||||
draw_betting();
|
||||
break;
|
||||
|
||||
case GameState::PLAYING:
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::DEALER_TURN:
|
||||
if (++dealer_timer >= 60) { // 1 second delay
|
||||
dealer_timer = 0;
|
||||
dealer_turn();
|
||||
}
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::GAME_OVER:
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::STATS:
|
||||
draw_stats();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_menu_static() {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_rules = *ui::Theme::getInstance()->fg_cyan;
|
||||
|
||||
painter.draw_string({84, 20}, style_title, "BLACKJACK");
|
||||
|
||||
// Draw rules
|
||||
painter.draw_string({70, 55}, style_rules, "-- RULES --");
|
||||
painter.draw_string({61, 75}, style_text, "Get close to 21");
|
||||
painter.draw_string({61, 90}, style_text, "without going over");
|
||||
painter.draw_string({61, 110}, style_text, "Dealer hits on 16");
|
||||
painter.draw_string({61, 125}, style_text, "Dealer stays on 17");
|
||||
painter.draw_string({61, 145}, style_text, "Blackjack pays 1:1");
|
||||
|
||||
// Controls
|
||||
painter.draw_string({65, 175}, style_rules, "-- CONTROLS --");
|
||||
painter.draw_string({61, 195}, style_text, "SELECT: Start/Hit");
|
||||
painter.draw_string({61, 210}, style_text, "LEFT: Stats");
|
||||
painter.draw_string({61, 225}, style_text, "RIGHT: Exit/Stay");
|
||||
|
||||
// Draw high score
|
||||
painter.draw_string({61, 250}, style_text, "High Score: $" + std::to_string(high_score));
|
||||
}
|
||||
|
||||
void BlackjackView::draw_menu() {
|
||||
// Only handle the flashing text animation
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
if (blink_state) {
|
||||
auto style = *ui::Theme::getInstance()->fg_yellow;
|
||||
painter.draw_string({55, 280}, style, "* PRESS SELECT *");
|
||||
} else {
|
||||
// Clear just the text area
|
||||
painter.fill_rectangle({55, 278, 130, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_stats() {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_value = *ui::Theme::getInstance()->fg_yellow;
|
||||
|
||||
painter.draw_string({75, 30}, style_title, "STATISTICS");
|
||||
|
||||
painter.draw_string({30, 80}, style_text, "Wins:");
|
||||
painter.draw_string({150, 80}, style_value, std::to_string(wins));
|
||||
|
||||
painter.draw_string({30, 100}, style_text, "Losses:");
|
||||
painter.draw_string({150, 100}, style_value, std::to_string(losses));
|
||||
|
||||
// Win percentage
|
||||
uint32_t total = wins + losses;
|
||||
if (total > 0) {
|
||||
uint32_t win_pct = (wins * 100) / total;
|
||||
painter.draw_string({30, 120}, style_text, "Win %:");
|
||||
painter.draw_string({150, 120}, style_value, std::to_string(win_pct) + "%");
|
||||
}
|
||||
|
||||
painter.draw_string({30, 160}, style_text, "High Score:");
|
||||
painter.draw_string({150, 160}, style_value, "$" + std::to_string(high_score));
|
||||
|
||||
painter.draw_string({30, 180}, style_text, "Cash:");
|
||||
painter.draw_string({150, 180}, style_value, "$" + std::to_string(cash));
|
||||
|
||||
painter.draw_string({40, 250}, style_text, "SELECT: Back");
|
||||
}
|
||||
|
||||
void BlackjackView::draw_betting() {
|
||||
static uint32_t last_bet = 0;
|
||||
static bool betting_drawn = false;
|
||||
|
||||
if (!betting_drawn) {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
painter.draw_string({70, 40}, style_title, "PLACE BET");
|
||||
painter.draw_string({30, 80}, style_text, "Cash: $" + std::to_string(cash));
|
||||
|
||||
painter.draw_string({30, 140}, style_text, "ENCODER: +/- $10");
|
||||
painter.draw_string({30, 160}, style_text, "SELECT: Deal");
|
||||
|
||||
betting_drawn = true;
|
||||
last_bet = 0;
|
||||
}
|
||||
|
||||
// Update bet display
|
||||
if (bet != last_bet) {
|
||||
painter.fill_rectangle({30, 110, 150, 20}, Color::black());
|
||||
painter.draw_string({30, 110}, *ui::Theme::getInstance()->fg_yellow, "Bet: $" + std::to_string(bet));
|
||||
last_bet = bet;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_game() {
|
||||
static int last_player_count = -1;
|
||||
static int last_dealer_count = -1;
|
||||
static GameState last_game_state = GameState::MENU;
|
||||
static uint32_t last_bet = 0;
|
||||
|
||||
// Clear and redraw if hands changed or game state changed
|
||||
if (player_card_count != last_player_count || dealer_card_count != last_dealer_count || game_state != last_game_state) {
|
||||
clear_screen();
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({10, 10}, style, "Cash: $" + std::to_string(cash));
|
||||
painter.draw_string({140, 10}, style, "Bet: $" + std::to_string(bet));
|
||||
|
||||
// Draw dealer hand with value next to label
|
||||
auto style_value = *ui::Theme::getInstance()->fg_yellow;
|
||||
painter.draw_string({10, 45}, *ui::Theme::getInstance()->fg_light, "Dealer:");
|
||||
if (!dealer_hidden || game_state == GameState::GAME_OVER) {
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
painter.draw_string({70, 45}, style_value, "(" + std::to_string(dealer_value) + ")");
|
||||
}
|
||||
draw_hand(10, 65, dealer_cards, dealer_card_count, true);
|
||||
|
||||
// Draw player hand with value next to label
|
||||
painter.draw_string({10, 165}, *ui::Theme::getInstance()->fg_light, "You:");
|
||||
int player_value = calculate_hand_value(player_cards, player_card_count);
|
||||
painter.draw_string({50, 165}, style_value, "(" + std::to_string(player_value) + ")");
|
||||
draw_hand(10, 185, player_cards, player_card_count, false);
|
||||
|
||||
// Draw controls or result
|
||||
if (game_state == GameState::PLAYING) {
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
painter.draw_string({30, 290}, style_text, "SELECT: Hit");
|
||||
painter.draw_string({130, 290}, style_text, "RIGHT: Stay");
|
||||
} else if (game_state == GameState::GAME_OVER) {
|
||||
const Style* style_result = ui::Theme::getInstance()->fg_yellow;
|
||||
std::string result;
|
||||
|
||||
if (player_value > 21) {
|
||||
result = "BUST! You Lose";
|
||||
style_result = ui::Theme::getInstance()->fg_red;
|
||||
} else if (calculate_hand_value(dealer_cards, dealer_card_count) > 21) {
|
||||
result = "Dealer Bust! Win!";
|
||||
style_result = ui::Theme::getInstance()->fg_green;
|
||||
} else if (player_value > calculate_hand_value(dealer_cards, dealer_card_count)) {
|
||||
result = "You Win!";
|
||||
style_result = ui::Theme::getInstance()->fg_green;
|
||||
} else if (player_value < calculate_hand_value(dealer_cards, dealer_card_count)) {
|
||||
result = "You Lose";
|
||||
style_result = ui::Theme::getInstance()->fg_red;
|
||||
} else {
|
||||
result = "Push (Tie)";
|
||||
}
|
||||
|
||||
// Draw result
|
||||
painter.draw_string({60, 270}, *style_result, result);
|
||||
|
||||
// Draw compact bet selector in top right area
|
||||
auto style_bet = *ui::Theme::getInstance()->fg_cyan;
|
||||
painter.draw_string({140, 25}, style_bet, "Next: $" + std::to_string(bet));
|
||||
|
||||
// Show controls
|
||||
painter.draw_string({10, 290}, *ui::Theme::getInstance()->fg_light, "SELECT: Deal ENC: +/-");
|
||||
}
|
||||
|
||||
last_player_count = player_card_count;
|
||||
last_dealer_count = dealer_card_count;
|
||||
last_game_state = game_state;
|
||||
last_bet = bet;
|
||||
} else if (game_state == GameState::GAME_OVER && bet != last_bet) {
|
||||
// Only update the bet display when it changes
|
||||
painter.fill_rectangle({140, 25, 90, 16}, Color::black());
|
||||
painter.draw_string({140, 25}, *ui::Theme::getInstance()->fg_cyan, "Next: $" + std::to_string(bet));
|
||||
last_bet = bet;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large) {
|
||||
if (large) {
|
||||
// Large suit symbols for center of card
|
||||
switch (suit) {
|
||||
case 0: // Spades
|
||||
// Top curve
|
||||
painter.fill_rectangle({x + 9, y + 4, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 8, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 7, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 6, y + 7, 8, 2}, color);
|
||||
painter.fill_rectangle({x + 5, y + 8, 10, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 9, 12, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 10, 14, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 11, 16, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 12, 18, 2}, color);
|
||||
painter.fill_rectangle({x + 0, y + 13, 20, 3}, color);
|
||||
painter.fill_rectangle({x + 1, y + 16, 18, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 17, 16, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 18, 14, 1}, color);
|
||||
// Stem
|
||||
painter.fill_rectangle({x + 9, y + 19, 2, 4}, color);
|
||||
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 23, 6, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 24, 8, 1}, color);
|
||||
break;
|
||||
|
||||
case 1: // Hearts
|
||||
// Left bump
|
||||
painter.fill_rectangle({x + 3, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 7, 8, 3}, color);
|
||||
painter.fill_rectangle({x + 0, y + 9, 9, 3}, color);
|
||||
// Right bump
|
||||
painter.fill_rectangle({x + 13, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 12, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 11, y + 7, 8, 3}, color);
|
||||
painter.fill_rectangle({x + 11, y + 9, 9, 3}, color);
|
||||
// Body
|
||||
painter.fill_rectangle({x + 1, y + 11, 18, 3}, color);
|
||||
painter.fill_rectangle({x + 2, y + 14, 16, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 16, 14, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 18, 12, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 19, 10, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 20, 8, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 21, 6, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 23, 2, 1}, color);
|
||||
break;
|
||||
|
||||
case 2: // Diamonds
|
||||
painter.fill_rectangle({x + 10, y + 3, 1, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 5, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 6, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 7, 9, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 8, 11, 1}, color);
|
||||
painter.fill_rectangle({x + 4, y + 9, 13, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 10, 15, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 11, 17, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 12, 19, 1}, color);
|
||||
painter.fill_rectangle({x + 0, y + 13, 21, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 14, 19, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 15, 17, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 16, 15, 1}, color);
|
||||
painter.fill_rectangle({x + 4, y + 17, 13, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 18, 11, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 19, 9, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 21, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 22, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 10, y + 23, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 3: // Clubs
|
||||
// Center circle
|
||||
painter.fill_rectangle({x + 8, y + 8, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 9, 7, 3}, color);
|
||||
painter.fill_rectangle({x + 8, y + 12, 5, 1}, color);
|
||||
// Left circle
|
||||
painter.fill_rectangle({x + 3, y + 11, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 12, 6, 3}, color);
|
||||
painter.fill_rectangle({x + 3, y + 15, 4, 1}, color);
|
||||
// Right circle
|
||||
painter.fill_rectangle({x + 14, y + 11, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 13, y + 12, 6, 3}, color);
|
||||
painter.fill_rectangle({x + 14, y + 15, 4, 1}, color);
|
||||
// Connect circles
|
||||
painter.fill_rectangle({x + 6, y + 13, 9, 2}, color);
|
||||
// Stem
|
||||
painter.fill_rectangle({x + 9, y + 16, 3, 4}, color);
|
||||
painter.fill_rectangle({x + 8, y + 19, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 21, 9, 1}, color);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Small suit symbols
|
||||
switch (suit) {
|
||||
case 0: // Spades - small
|
||||
painter.fill_rectangle({x + 2, y + 1, 3, 3}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 5, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 2}, color);
|
||||
break;
|
||||
|
||||
case 1: // Hearts - small
|
||||
painter.fill_rectangle({x + 1, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 2, 5, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 2: // Diamonds - small
|
||||
painter.fill_rectangle({x + 3, y + 1, 1, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 2, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 3: // Clubs - small
|
||||
painter.fill_rectangle({x + 3, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 3, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 2, 2}, color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_card(int x, int y, uint8_t card, bool hidden) {
|
||||
const int width = 60;
|
||||
const int height = 80;
|
||||
|
||||
// Draw card background
|
||||
painter.fill_rectangle({x, y, width, height}, Color::white());
|
||||
painter.draw_rectangle({x, y, width, height}, Color::black());
|
||||
painter.draw_rectangle({x + 1, y + 1, width - 2, height - 2}, Color::grey()); // Inner border
|
||||
|
||||
if (hidden) {
|
||||
// Draw card back pattern - diagonal lines
|
||||
for (int i = 4; i < width - 4; i += 6) {
|
||||
for (int j = 4; j < height - 4; j += 6) {
|
||||
painter.fill_rectangle({x + i, y + j, 3, 3}, Color::blue());
|
||||
painter.fill_rectangle({x + i + 3, y + j + 3, 3, 3}, Color::red());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Draw card value
|
||||
uint8_t suit = get_card_suit(card);
|
||||
Color suit_color = (suit == 1 || suit == 2) ? Color::red() : Color::black();
|
||||
|
||||
const auto* base_style = ui::Theme::getInstance()->fg_light;
|
||||
Style card_style{
|
||||
.font = base_style->font,
|
||||
.background = Color::white(),
|
||||
.foreground = suit_color};
|
||||
|
||||
std::string value_str = get_card_string(card);
|
||||
|
||||
// Draw value in top-left corner
|
||||
painter.draw_string({x + 4, y + 4}, card_style, value_str);
|
||||
|
||||
// Draw small suit symbol next to value
|
||||
int suit_x = (value_str == "10") ? x + 20 : x + 12;
|
||||
draw_suit_symbol(suit_x, y + 4, suit, suit_color, false);
|
||||
|
||||
// Draw value in bottom-right corner
|
||||
int bottom_x = (value_str == "10") ? x + width - 24 : x + width - 16;
|
||||
painter.draw_string({bottom_x, y + height - 18}, card_style, value_str);
|
||||
|
||||
// Draw small suit symbol in bottom-right
|
||||
draw_suit_symbol(x + width - 10, y + height - 16, suit, suit_color, false);
|
||||
|
||||
// Draw large suit symbol in center
|
||||
draw_suit_symbol(x + 20, y + 28, suit, suit_color, true);
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer) {
|
||||
// Calculate total width needed
|
||||
const int card_width = 60;
|
||||
const int overlap = 40; // Amount of overlap when cards need to fit
|
||||
const int max_width = 230 - x; // Available width on screen
|
||||
|
||||
int spacing;
|
||||
if (count == 1) {
|
||||
spacing = 0;
|
||||
} else if (count == 2) {
|
||||
spacing = card_width + 5; // Small gap for 2 cards
|
||||
} else {
|
||||
// Calculate spacing to fit all cards
|
||||
int total_overlap_width = card_width + (count - 1) * overlap;
|
||||
if (total_overlap_width <= max_width) {
|
||||
spacing = overlap;
|
||||
} else {
|
||||
// Need more overlap to fit
|
||||
spacing = (max_width - card_width) / (count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
bool hide = is_dealer && dealer_hidden && i == 1;
|
||||
int card_x = x + (i * spacing);
|
||||
draw_card(card_x, y, cards[i], hide);
|
||||
}
|
||||
}
|
||||
|
||||
bool BlackjackView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
switch (game_state) {
|
||||
case GameState::MENU:
|
||||
if (cash < 10) {
|
||||
cash = 100; // Reset if broke - maybe should provide https://gamblersanonymous.org/ link if they also lost their wife and house
|
||||
wins = 0;
|
||||
losses = 0;
|
||||
}
|
||||
game_state = GameState::BETTING;
|
||||
break;
|
||||
|
||||
case GameState::BETTING:
|
||||
deal_cards();
|
||||
break;
|
||||
|
||||
case GameState::PLAYING:
|
||||
player_hit();
|
||||
break;
|
||||
|
||||
case GameState::GAME_OVER:
|
||||
// Deal new hand with current bet
|
||||
if (cash >= bet) {
|
||||
deal_cards();
|
||||
} else if (cash >= 10) {
|
||||
// Not enough for current bet, reduce to what they can afford
|
||||
bet = 10;
|
||||
deal_cards();
|
||||
} else {
|
||||
// Broke, reset game
|
||||
cash = 100;
|
||||
wins = 0;
|
||||
losses = 0;
|
||||
game_state = GameState::MENU;
|
||||
draw_menu_static();
|
||||
}
|
||||
break;
|
||||
|
||||
case GameState::STATS:
|
||||
game_state = GameState::MENU;
|
||||
draw_menu_static();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
} else if (key == KeyEvent::Left) {
|
||||
if (game_state == GameState::MENU) {
|
||||
game_state = GameState::STATS;
|
||||
}
|
||||
return true;
|
||||
} else if (key == KeyEvent::Right) {
|
||||
if (game_state == GameState::MENU) {
|
||||
nav_.pop();
|
||||
return true;
|
||||
} else if (game_state == GameState::PLAYING) {
|
||||
player_stay();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlackjackView::on_encoder(const EncoderEvent delta) {
|
||||
if (game_state == GameState::BETTING || game_state == GameState::GAME_OVER) {
|
||||
if (delta > 0 && bet + 10 <= cash) {
|
||||
bet += 10;
|
||||
} else if (delta < 0 && bet >= 20) {
|
||||
bet -= 10;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::blackjack
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#ifndef __UI_BLACKJACK_H__
|
||||
#define __UI_BLACKJACK_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
|
||||
// Game states
|
||||
enum class GameState {
|
||||
MENU,
|
||||
BETTING,
|
||||
PLAYING,
|
||||
DEALER_TURN,
|
||||
GAME_OVER,
|
||||
STATS
|
||||
};
|
||||
|
||||
// Card structure
|
||||
struct Card {
|
||||
uint8_t value; // 1-13 (Ace=1, Jack=11, Queen=12, King=13)
|
||||
uint8_t suit; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
|
||||
|
||||
Card()
|
||||
: value(0), suit(0) {}
|
||||
Card(uint8_t v, uint8_t s)
|
||||
: value(v), suit(s) {}
|
||||
};
|
||||
|
||||
// Timer class
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
// Function declarations
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
class BlackjackView : public View {
|
||||
public:
|
||||
BlackjackView(NavigationView& nav);
|
||||
~BlackjackView();
|
||||
|
||||
void on_show() override;
|
||||
std::string title() const override { return "Blackjack"; }
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
// Public for timer callback
|
||||
GameState game_state = GameState::MENU;
|
||||
void update_game();
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
bool initialized = false;
|
||||
|
||||
// Game variables
|
||||
static constexpr uint8_t MAX_CARDS_IN_HAND = 11; // Maximum possible cards before bust
|
||||
uint8_t deck[52];
|
||||
uint8_t deck_position = 0;
|
||||
|
||||
uint8_t player_cards[MAX_CARDS_IN_HAND];
|
||||
uint8_t player_card_count = 0;
|
||||
|
||||
uint8_t dealer_cards[MAX_CARDS_IN_HAND];
|
||||
uint8_t dealer_card_count = 0;
|
||||
|
||||
// Game state variables
|
||||
uint32_t cash = 100;
|
||||
uint32_t bet = 10;
|
||||
uint32_t wins = 0;
|
||||
uint32_t losses = 0;
|
||||
uint32_t high_score = 100;
|
||||
bool dealer_hidden = true;
|
||||
|
||||
// UI state
|
||||
uint8_t menu_selection = 0;
|
||||
bool blink_state = true;
|
||||
uint32_t blink_counter = 0;
|
||||
uint32_t dealer_timer = 0;
|
||||
|
||||
// Timer
|
||||
Ticker game_timer;
|
||||
|
||||
// Methods
|
||||
void init_deck();
|
||||
void shuffle_deck();
|
||||
uint8_t draw_card();
|
||||
void deal_cards();
|
||||
int calculate_hand_value(uint8_t* cards, uint8_t count);
|
||||
int get_card_value(uint8_t card);
|
||||
uint8_t get_card_suit(uint8_t card);
|
||||
std::string get_card_string(uint8_t card);
|
||||
void player_hit();
|
||||
void player_stay();
|
||||
void dealer_turn();
|
||||
void check_game_over();
|
||||
void reset_game();
|
||||
|
||||
// Drawing methods
|
||||
void draw_menu();
|
||||
void draw_menu_static();
|
||||
void draw_stats();
|
||||
void draw_game();
|
||||
void draw_betting();
|
||||
void draw_card(int x, int y, uint8_t card, bool hidden = false);
|
||||
void draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer = false);
|
||||
void draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large);
|
||||
void clear_screen();
|
||||
|
||||
// Settings
|
||||
app_settings::SettingsManager settings_{
|
||||
"blackjack",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"cash"sv, &cash},
|
||||
{"wins"sv, &wins},
|
||||
{"losses"sv, &losses},
|
||||
{"highscore"sv, &high_score}}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::blackjack
|
||||
|
||||
#endif /* __UI_BLACKJACK_H__ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user