mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 02:52:58 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31c53dc455 | |||
| ff12d06757 | |||
| 5b540fa659 | |||
| 9cccfa63e5 | |||
| 6c838256df | |||
| c8f236a708 | |||
| 4641dcb449 | |||
| 458a6634bf | |||
| b8a64598ba | |||
| 09dff447de | |||
| 079329fc6c | |||
| 674059801d | |||
| 5020e5bd28 | |||
| c31fef0535 | |||
| a153cd741d | |||
| 5851b47f99 | |||
| 83a488e5b9 | |||
| 110a543ed3 | |||
| da5322765d | |||
| 8df05a57ea | |||
| bea9f444c6 | |||
| abd6177303 | |||
| d6a9c74665 | |||
| 5bb23b636e | |||
| d5296c6ac3 | |||
| c9863187d6 | |||
| 13e4f225ea | |||
| 0df2c085a1 | |||
| a4c2e155e5 | |||
| c90f0944b1 | |||
| 7a38b04192 | |||
| 9f84ccbe1f | |||
| cb3774ad81 | |||
| cc7123b28d | |||
| 8e945024c5 | |||
| 7d28e495c2 | |||
| 48129cdf8d | |||
| 66aa20161d | |||
| 1b3359b050 | |||
| 8d1fb7cb66 | |||
| e01a184721 | |||
| 26b830b816 | |||
| a223bd1f21 | |||
| 023cee2c3c | |||
| fcc52e83de | |||
| dcaa02c1e1 |
+6
-2
@@ -23,6 +23,10 @@ cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
|
||||
set(CMAKE_COLOR_MAKEFILE ON)
|
||||
|
||||
add_compile_options(-fdiagnostics-color=always)
|
||||
|
||||
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
|
||||
|
||||
project(portapack-h1)
|
||||
@@ -84,12 +88,12 @@ if(USE_CCACHE)
|
||||
if(CCACHE_FOUND)
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_FOUND})
|
||||
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_FOUND})
|
||||
message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` to turn off ccache")
|
||||
message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=OFF .. ` (cmake with Ninja) to turn off ccache")
|
||||
else()
|
||||
message(WARNING "ccache is enabled but not found on the system.")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` to turn on ccache")
|
||||
message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=ON ..` (cmake with Ninja) to turn on ccache")
|
||||
endif()
|
||||
message(STATUS "-------^ ccache info--------")
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://grabify.link/ZYYPLB) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
||||
*[PortaPack H2+HackRF+battery](https://grabify.link/P9CO0J) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure)*
|
||||
|
||||
# What is this?
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||
|
||||
[<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="701">](https://grabify.link/0JUHZ6)
|
||||
[<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="701">](https://grabify.link/F9MPOO)
|
||||
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://grabify.link/X4D5TF) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="172">](https://grabify.link/9UZGEW) [<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="172">](https://grabify.link/5MU2VH) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://grabify.link/C0J6ZR)
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://grabify.link/X4D5TF) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="172">](https://grabify.link/9UZGEW) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://grabify.link/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://grabify.link/C0J6ZR)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
@@ -27,11 +27,11 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||
## What to buy?
|
||||
|
||||
:heavy_check_mark:  The fabulous [PortaPack H4M Mayhem](https://grabify.link/VPMPSL), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://grabify.link/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
:heavy_check_mark:  The fabulous [PortaPack H4M Mayhem](https://opensourcesdrlab.com/products/h4m-receiver-and-spectrum-analyzer), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://grabify.link/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/ZYYPLB), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/P9CO0J), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/XNGTAP), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||
|
||||
@@ -55,7 +55,7 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
|
||||
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
||||
|
||||
## What if I really want something specific?
|
||||
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?assignees=&labels=enhancement&projects=&template=02_feature_request.yml). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||
|
||||
## What if I need help?
|
||||
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
|
||||
|
||||
@@ -117,6 +117,7 @@ set(CSRC
|
||||
usb_serial_descriptor.c
|
||||
usb_serial_endpoints.c
|
||||
usb_serial_device_to_host.c
|
||||
i2c_device_to_host.c
|
||||
${HACKRF_PATH}/firmware/common/usb.c
|
||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
@@ -191,7 +192,6 @@ set(CPPSRC
|
||||
core_control.cpp
|
||||
database.cpp
|
||||
de_bruijn.cpp
|
||||
#emu_cc1101.cpp
|
||||
rfm69.cpp
|
||||
event_m0.cpp
|
||||
file_reader.cpp
|
||||
@@ -246,7 +246,6 @@ set(CPPSRC
|
||||
hw/touch_adc.cpp
|
||||
ui_baseband_stats_view.cpp
|
||||
ui_navigation.cpp
|
||||
ui_playdead.cpp
|
||||
ui_record_view.cpp
|
||||
ui_sd_card_status_view.cpp
|
||||
ui/ui_alphanum.cpp
|
||||
@@ -270,47 +269,34 @@ set(CPPSRC
|
||||
ui/ui_bmpview.cpp
|
||||
apps/ais_app.cpp
|
||||
apps/analog_audio_app.cpp
|
||||
# apps/analog_tv_app.cpp
|
||||
apps/ble_comm_app.cpp
|
||||
apps/ble_rx_app.cpp
|
||||
apps/ble_tx_app.cpp
|
||||
apps/capture_app.cpp
|
||||
apps/ert_app.cpp
|
||||
# apps/gps_sim_app.cpp
|
||||
# apps/lge_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
# apps/tpms_app.cpp
|
||||
apps/ui_about_simple.cpp
|
||||
apps/ui_adsb_rx.cpp
|
||||
# apps/ui_adsb_tx.cpp #moved to ext
|
||||
apps/ui_aprs_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
apps/ui_battinfo.cpp
|
||||
apps/ui_bht_tx.cpp
|
||||
apps/ui_bmp_file_viewer.cpp
|
||||
apps/ui_btle_rx.cpp
|
||||
# apps/ui_coasterp.cpp
|
||||
apps/ui_debug.cpp
|
||||
apps/ui_debug_max17055.cpp
|
||||
apps/ui_dfu_menu.cpp
|
||||
apps/ui_encoders.cpp
|
||||
apps/ui_external_module_view.cpp
|
||||
apps/ui_fileman.cpp
|
||||
apps/ui_flash_utility.cpp
|
||||
apps/ui_freqman.cpp
|
||||
apps/ui_fsk_rx.cpp
|
||||
apps/ui_iq_trim.cpp
|
||||
# apps/ui_jammer.cpp
|
||||
# apps/ui_keyfob.cpp
|
||||
# apps/ui_lcr.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_looking_glass_app.cpp
|
||||
apps/ui_mictx.cpp
|
||||
apps/ui_modemsetup.cpp
|
||||
# apps/ui_morse.cpp
|
||||
# apps/ui_nrf_rx.cpp
|
||||
# apps/ui_nuoptix.cpp
|
||||
apps/ui_playlist.cpp
|
||||
apps/ui_pocsag_tx.cpp
|
||||
apps/ui_rds.cpp
|
||||
@@ -324,16 +310,11 @@ set(CPPSRC
|
||||
apps/ui_settings.cpp
|
||||
apps/ui_siggen.cpp
|
||||
apps/ui_sonde.cpp
|
||||
# apps/ui_spectrum_painter_image.cpp
|
||||
# apps/ui_spectrum_painter_text.cpp
|
||||
# apps/ui_spectrum_painter.cpp
|
||||
apps/ui_ss_viewer.cpp
|
||||
# apps/ui_sstvtx.cpp #moved to ext
|
||||
apps/ui_standalone_view.cpp
|
||||
apps/ui_subghzd.cpp
|
||||
# apps/ui_test.cpp
|
||||
apps/ui_text_editor.cpp
|
||||
apps/ui_tone_search.cpp
|
||||
apps/ui_touch_calibration.cpp
|
||||
apps/ui_touchtunes.cpp
|
||||
apps/ui_view_wav.cpp
|
||||
@@ -344,14 +325,8 @@ set(CPPSRC
|
||||
protocols/bht.cpp
|
||||
protocols/dcs.cpp
|
||||
protocols/encoders.cpp
|
||||
# protocols/lcr.cpp
|
||||
protocols/modems.cpp
|
||||
protocols/rds.cpp
|
||||
# ui_handwrite.cpp
|
||||
# ui_loadmodule.cpp
|
||||
# ui_numbers.cpp
|
||||
# ui_replay_view.cpp
|
||||
# ui_script.cpp
|
||||
ui_sd_card_debug.cpp
|
||||
config_mode.cpp
|
||||
${CPLD_20150901_DATA_CPP}
|
||||
|
||||
@@ -87,9 +87,12 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||
|
||||
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz
|
||||
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
|
||||
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
|
||||
option_format.set_selected_index(1);
|
||||
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
|
||||
}
|
||||
if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
|
||||
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
|
||||
}
|
||||
previous_bandwidth = bandwidth;
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyleft (ↄ) 2022 NotPike
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "replay_app.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ReplayAppView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
file_path = new_file_path;
|
||||
File::Size file_size{};
|
||||
|
||||
{ // Get the size of the data file.
|
||||
File data_file;
|
||||
auto error = data_file.open(file_path);
|
||||
if (error) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
file_size = data_file.size();
|
||||
}
|
||||
|
||||
// Get original record frequency if available.
|
||||
auto metadata_path = get_metadata_path(file_path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
if (metadata) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
// TODO: This is interesting because it implies that the
|
||||
// The capture will just be replayed at the freq set on the
|
||||
// FrequencyField. Is that an intentional behavior?
|
||||
sample_rate = 500000;
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
||||
auto duration = ms_duration(file_size, sample_rate, sample_size);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void ReplayAppView::on_tx_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void ReplayAppView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
void ReplayAppView::file_error() {
|
||||
nav_.display_modal("Error", "File read error.");
|
||||
}
|
||||
|
||||
bool ReplayAppView::is_active() const {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void ReplayAppView::toggle() {
|
||||
if (is_active()) {
|
||||
stop(false);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayAppView::start() {
|
||||
stop(false);
|
||||
|
||||
std::unique_ptr<stream::Reader> reader;
|
||||
|
||||
auto p = std::make_unique<FileConvertReader>();
|
||||
auto open_error = p->open(file_path);
|
||||
if (open_error.is_valid()) {
|
||||
file_error();
|
||||
return; // Fixes TX bug if there's a file error
|
||||
} else {
|
||||
reader = std::move(p);
|
||||
}
|
||||
|
||||
if (reader) {
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
baseband::set_sample_rate(sample_rate, OversampleRate::x8);
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size, buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
transmitter_model.set_sampling_rate(sample_rate * toUType(OversampleRate::x8));
|
||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||
transmitter_model.enable();
|
||||
|
||||
if (portapack::persistent_memory::stealth_mode()) {
|
||||
DisplaySleepMessage message;
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayAppView::stop(const bool do_loop) {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
if (do_loop && check_loop.value()) {
|
||||
start();
|
||||
} else {
|
||||
transmitter_model.disable();
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
}
|
||||
|
||||
ready_signal = false;
|
||||
}
|
||||
|
||||
void ReplayAppView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
stop(true);
|
||||
} else if (return_code == ReplayThread::READ_ERROR) {
|
||||
stop(false);
|
||||
file_error();
|
||||
}
|
||||
|
||||
progressbar.set_value(0);
|
||||
}
|
||||
|
||||
ReplayAppView::ReplayAppView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
add_children({
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&text_sample_rate,
|
||||
&text_duration,
|
||||
&progressbar,
|
||||
&field_frequency,
|
||||
&tx_view, // now it handles previous rfgain, rfamp.
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C*");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
ReplayAppView::~ReplayAppView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void ReplayAppView::on_hide() {
|
||||
stop(false);
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __REPLAY_APP_HPP__
|
||||
#define __REPLAY_APP_HPP__
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ReplayAppView : public View {
|
||||
public:
|
||||
ReplayAppView(NavigationView& nav);
|
||||
~ReplayAppView();
|
||||
|
||||
void on_hide() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Replay"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_replay", app_settings::Mode::TX};
|
||||
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
uint32_t sample_rate = 0;
|
||||
int32_t tx_gain{47};
|
||||
bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
const size_t read_size{16384};
|
||||
const size_t buffer_count{3};
|
||||
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
|
||||
void toggle();
|
||||
void start();
|
||||
void stop(const bool do_loop);
|
||||
bool is_active() const;
|
||||
void set_ready();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void file_error();
|
||||
|
||||
std::filesystem::path file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
"Open file"};
|
||||
|
||||
Text text_filename{
|
||||
{11 * 8, 0 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
Text text_sample_rate{
|
||||
{24 * 8, 0 * 16, 6 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Text text_duration{
|
||||
{11 * 8, 1 * 16, 6 * 8, 16},
|
||||
"-"};
|
||||
ProgressBar progressbar{
|
||||
{18 * 8, 1 * 16, 12 * 8, 16}};
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 2 * 16},
|
||||
nav_};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 2 * 16},
|
||||
/*short_ui*/ true};
|
||||
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
true};
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
this->handle_replay_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->set_ready();
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__REPLAY_APP_HPP__*/
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "ui_about.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <cstring>
|
||||
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
// This is pretty much WaterfallView but in the opposite direction
|
||||
CreditsWidget::CreditsWidget(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect} {
|
||||
}
|
||||
|
||||
void CreditsWidget::paint(Painter&) {
|
||||
}
|
||||
|
||||
void CreditsWidget::on_show() {
|
||||
clear();
|
||||
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
}
|
||||
|
||||
void CreditsWidget::on_hide() {
|
||||
display.scroll_disable();
|
||||
}
|
||||
|
||||
void CreditsWidget::new_row(
|
||||
const std::array<Color, 240>& pixel_row) {
|
||||
// Glitch be here (see comment in main.cpp)
|
||||
const auto draw_y = display.scroll(-1);
|
||||
|
||||
display.draw_pixels(
|
||||
{{0, draw_y - 1}, {240, 1}},
|
||||
pixel_row);
|
||||
}
|
||||
|
||||
void CreditsWidget::clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Theme::getInstance()->bg_darkest->background);
|
||||
}
|
||||
|
||||
void AboutView::update() {
|
||||
size_t i = 0;
|
||||
std::array<Color, 240> pixel_row;
|
||||
|
||||
slow_down++;
|
||||
if (slow_down % 3 < 2) return;
|
||||
|
||||
if (!timer) {
|
||||
if (loop) {
|
||||
credits_index = 0;
|
||||
loop = false;
|
||||
}
|
||||
|
||||
text = credits[credits_index].text;
|
||||
timer = credits[credits_index].delay;
|
||||
start_pos = credits[credits_index].start_pos;
|
||||
|
||||
if (timer < 0) {
|
||||
timer = 240;
|
||||
loop = true;
|
||||
} else
|
||||
timer += 16;
|
||||
|
||||
render_line = 0;
|
||||
credits_index++;
|
||||
} else
|
||||
timer--;
|
||||
|
||||
if (render_line < 16) {
|
||||
for (const auto c : text) {
|
||||
const auto glyph = style().font.glyph(c);
|
||||
|
||||
const size_t start = (glyph.size().width() / 8) * render_line;
|
||||
for (Dim c = 0; c < glyph.size().width(); c++) {
|
||||
const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
|
||||
pixel_row[start_pos + i + c] = pixel ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background;
|
||||
}
|
||||
|
||||
const auto advance = glyph.advance();
|
||||
i += advance.x();
|
||||
}
|
||||
render_line++;
|
||||
}
|
||||
|
||||
credits_display.new_row(pixel_row);
|
||||
}
|
||||
|
||||
AboutView::AboutView(
|
||||
NavigationView& nav) {
|
||||
add_children({&credits_display,
|
||||
&button_ok});
|
||||
|
||||
button_ok.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_ABOUT_H__
|
||||
#define __UI_ABOUT_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class CreditsWidget : public Widget {
|
||||
public:
|
||||
CreditsWidget(Rect parent_rect);
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter&) override;
|
||||
|
||||
void new_row(const std::array<Color, 240>& pixel_row);
|
||||
|
||||
private:
|
||||
void clear();
|
||||
};
|
||||
|
||||
class AboutView : public View {
|
||||
public:
|
||||
AboutView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "About"; };
|
||||
|
||||
private:
|
||||
void update();
|
||||
|
||||
uint8_t credits_index{0};
|
||||
uint8_t render_line{0};
|
||||
Coord start_pos{0};
|
||||
uint8_t slow_down{0};
|
||||
int32_t timer{0};
|
||||
bool loop{false};
|
||||
|
||||
std::string text{};
|
||||
|
||||
typedef struct credits_t {
|
||||
size_t start_pos;
|
||||
std::string text;
|
||||
int32_t delay;
|
||||
} credits_t;
|
||||
|
||||
// TODO: Make this dinamically centered and parse \n as the delay value so it is easy to maintain
|
||||
const credits_t credits[26] = {
|
||||
// 012345678901234567890123456789
|
||||
{60, "PortaPack Mayhem", 0},
|
||||
{60, "PortaPack|HAVOC", 0},
|
||||
{11 * 8, "Gurus J. Boone", 0},
|
||||
{18 * 8, "M. Ossmann", 16},
|
||||
{11 * 8, "HAVOC Furrtek", 16},
|
||||
{7 * 8, "POCSAG rx T. Sailer", 0},
|
||||
{18 * 8, "E. Oenal", 16},
|
||||
{0 * 8, "Radiosonde infos F4GMU", 0},
|
||||
{18 * 8, "RS1729", 16},
|
||||
{4 * 8, "RDS waveform C. Jacquet", 16},
|
||||
{7 * 8, "Xy. infos cLx", 16},
|
||||
{2 * 8, "OOK scan trick Samy Kamkar", 16},
|
||||
{7 * 8, "World map NASA", 16},
|
||||
{0 * 8, "TouchTunes infos Notpike", 16},
|
||||
{4 * 8, "Subaru infos Tom", 0},
|
||||
{18 * 8, "Wimmenhove", 16},
|
||||
{1 * 8, "GPS,TV,BTLE,NRF Shao", 24},
|
||||
{6 * 8, "Thanks & donators", 16},
|
||||
{1 * 8, "Rainer Matla Keld Norman", 0},
|
||||
{1 * 8, " Giorgio C. DC1RDB", 0},
|
||||
{1 * 8, " Sigmounte Waax", 0},
|
||||
{1 * 8, " Windyoona Channels", 0},
|
||||
{1 * 8, " F4GEV Pyr3x", 0},
|
||||
{1 * 8, " HB3YOE", 24},
|
||||
{11 * 8, "MMXVIII", -1}};
|
||||
|
||||
CreditsWidget credits_display{
|
||||
{0, 16, 240, 240}};
|
||||
|
||||
Button button_ok{
|
||||
{72, 272, 96, 24},
|
||||
"OK"};
|
||||
|
||||
MessageHandlerRegistration message_handler_update{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->update();
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_ABOUT_H__*/
|
||||
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <ch.h>
|
||||
|
||||
#include "demofont.hpp"
|
||||
#include "ymdata.hpp"
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "ui_about.hpp"
|
||||
#include "touch.hpp"
|
||||
#include "sine_table.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <cstring>
|
||||
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void AboutView::on_show() {
|
||||
transmitter_model.set_target_frequency(1337000000); // TODO: Change
|
||||
transmitter_model.set_baseband_configuration({
|
||||
.mode = 0,
|
||||
.sampling_rate = 1536000,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_lna(40);
|
||||
transmitter_model.set_vga(40);
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_audiotx_data(32, 50, false, 0);
|
||||
|
||||
// audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
|
||||
}
|
||||
|
||||
void AboutView::render_video() {
|
||||
uint8_t p, r, luma, chroma, cy;
|
||||
ui::Color cc;
|
||||
char ch;
|
||||
float s;
|
||||
|
||||
// Send framebuffer to LCD. Gotta go fast !
|
||||
display.render_box({30, 112}, {180, 72}, framebuffer);
|
||||
|
||||
// Clear framebuffer to black
|
||||
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
||||
|
||||
// Drum hit palette animation
|
||||
if (drum > 1) drum--;
|
||||
|
||||
// Render copper bars from Y buffer
|
||||
for (p = 0; p < 72; p++) {
|
||||
luma = copperbuffer[p] & 0x0F; // 0 is transparent
|
||||
if (luma) {
|
||||
chroma = copperbuffer[p] >> 4;
|
||||
cc = ui::Color(std::min((coppercolor[chroma][0] / luma) * drum, 255), std::min((coppercolor[chroma][1] / luma) * drum, 255), std::min((coppercolor[chroma][2] / luma) * drum, 255));
|
||||
for (r = 0; r < 180; r++)
|
||||
framebuffer[(p * 180) + r] = cc;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll in/out state machine
|
||||
if (anim_state == 0) {
|
||||
// Scroll in
|
||||
if (ofy < 8) {
|
||||
ofy++;
|
||||
anim_state = 0;
|
||||
} else {
|
||||
anim_state = 1;
|
||||
}
|
||||
if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
|
||||
ofx += 8;
|
||||
anim_state = 0;
|
||||
}
|
||||
} else if (anim_state == 1) {
|
||||
// Just wait
|
||||
if (credits_timer == (30 * 3)) {
|
||||
credits_timer = 0;
|
||||
anim_state = 2;
|
||||
} else {
|
||||
credits_timer++;
|
||||
}
|
||||
} else {
|
||||
// Scroll out
|
||||
if (credits[credits_index].change == true) {
|
||||
if (ofy > -24) {
|
||||
ofy--;
|
||||
anim_state = 2;
|
||||
} else {
|
||||
anim_state = 0;
|
||||
}
|
||||
} else {
|
||||
anim_state = 0;
|
||||
}
|
||||
if (ofx < 180) {
|
||||
ofx += 8;
|
||||
anim_state = 2;
|
||||
}
|
||||
|
||||
// Switch to next text
|
||||
if (anim_state == 0) {
|
||||
if (credits_index == 9)
|
||||
credits_index = 0;
|
||||
else
|
||||
credits_index++;
|
||||
ofx = -(strlen(credits[credits_index].name) * 16) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Sine text ("role")
|
||||
p = 0;
|
||||
while ((ch = credits[credits_index].role[p])) {
|
||||
draw_demoglyph({(ui::Coord)(8 + (p * 16)), (ui::Coord)(ofy + (sine_table_f32[((p * 16) + (phase >> 5)) & 0xFF] * 8))}, ch, paletteA);
|
||||
p++;
|
||||
}
|
||||
|
||||
// Scroll text (name)
|
||||
p = 0;
|
||||
while ((ch = credits[credits_index].name[p])) {
|
||||
draw_demoglyph({(ui::Coord)(ofx + (p * 16)), 56}, ch, paletteB);
|
||||
p++;
|
||||
}
|
||||
|
||||
// Clear bars Y buffer
|
||||
memset(copperbuffer, 0, 72);
|
||||
|
||||
// Render bars to Y buffer
|
||||
for (p = 0; p < 5; p++) {
|
||||
cy = copperbars[p];
|
||||
for (r = 0; r < 16; r++)
|
||||
copperbuffer[cy + r] = copperluma[r] + (p << 4);
|
||||
}
|
||||
|
||||
// Animate bars positions
|
||||
for (p = 0; p < 5; p++) {
|
||||
s = sine_table_f32[((p * 32) + (phase / 24)) & 0xFF];
|
||||
s += sine_table_f32[((p * 16) + (phase / 35)) & 0xFF];
|
||||
copperbars[p] = 28 + (uint8_t)(s * 14);
|
||||
}
|
||||
|
||||
phase += 128;
|
||||
}
|
||||
|
||||
void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
|
||||
uint8_t x, y, c, cl, cr;
|
||||
uint16_t che;
|
||||
int16_t lbx, il;
|
||||
|
||||
// Map ASCII to font bitmap
|
||||
if ((ch >= 32) && (ch < 96))
|
||||
che = char_map[ch - 32];
|
||||
else
|
||||
che = 0xFF;
|
||||
|
||||
if (che < 0xFF) {
|
||||
che = (che * 128) + 48; // Start in bitmap
|
||||
|
||||
il = (180 * p.y) + p.x; // Start il framebuffer
|
||||
|
||||
for (y = 0; y < 16; y++) {
|
||||
if (p.y + y >= 72) break; // Over bottom of framebuffer, abort
|
||||
if (p.y + y >= 0) {
|
||||
for (x = 0; x < 8; x++) {
|
||||
c = demofont_bin[x + (y * 8) + che]; // Split byte in 2 4BPP pixels
|
||||
cl = c >> 4;
|
||||
cr = c & 0x0F;
|
||||
lbx = p.x + (x * 2);
|
||||
if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
|
||||
lbx++;
|
||||
il++;
|
||||
if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
|
||||
il++;
|
||||
}
|
||||
il += 180 - 16;
|
||||
} else {
|
||||
il += 180;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::render_audio() {
|
||||
uint8_t i, ymdata;
|
||||
uint16_t ym_render_cnt;
|
||||
|
||||
// This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet
|
||||
|
||||
// Render 1024 music samples
|
||||
for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
|
||||
// Update registers at 48000/960 = 50Hz
|
||||
if (ym_sample_cnt == 0) {
|
||||
// "Decompress" on the fly and update YM registers
|
||||
for (i = 0; i < 14; i++) {
|
||||
if (!ym_regs[i].cnt) {
|
||||
// New run
|
||||
ymdata = ymdata_bin[ym_regs[i].ptr++];
|
||||
ym_regs[i].cnt = ymdata & 0x7F;
|
||||
if (ymdata & 0x80) {
|
||||
ym_regs[i].same = true;
|
||||
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
||||
} else {
|
||||
ym_regs[i].same = false;
|
||||
}
|
||||
// Detect drum on channel B
|
||||
if (i == 3)
|
||||
if (ym_regs[3].value > 2) drum = 4;
|
||||
}
|
||||
if (ym_regs[i].same == false) {
|
||||
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
||||
if (i == 13) {
|
||||
// Update envelope attributes
|
||||
ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
|
||||
if (!(ym_regs[13].value & 8)) {
|
||||
ym_env_hold = 1;
|
||||
ym_env_alt = ym_env_att;
|
||||
} else {
|
||||
ym_env_hold = ym_regs[13].value & 1;
|
||||
ym_env_alt = ym_regs[13].value & 2;
|
||||
}
|
||||
// Reset envelope counter
|
||||
ym_env_step = 0x1F;
|
||||
ym_env_holding = 0;
|
||||
ym_env_vol = (ym_env_step ^ ym_env_att);
|
||||
}
|
||||
}
|
||||
ym_regs[i].cnt--;
|
||||
}
|
||||
ym_frame++;
|
||||
}
|
||||
|
||||
// Square wave oscillators
|
||||
// 2457600/16/48000 = 3.2, but 4 sounds better than 3...
|
||||
for (i = 0; i < 3; i++) {
|
||||
ym_osc_cnt[i] += 4;
|
||||
if (ym_osc_cnt[i] >= (ym_regs[i * 2].value | ((ym_regs[(i * 2) + 1].value & 0x0f) << 8))) {
|
||||
ym_osc_cnt[i] = 0;
|
||||
ym_osc_out[i] ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Noise generator
|
||||
ym_noise_cnt += 4;
|
||||
if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
|
||||
ym_noise_cnt = 0;
|
||||
ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
|
||||
ym_rng >>= 1;
|
||||
}
|
||||
|
||||
// Mix tones and noise
|
||||
for (i = 0; i < 3; i++)
|
||||
ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));
|
||||
|
||||
// Envelope generator
|
||||
if (!ym_env_holding) {
|
||||
ym_env_cnt += 8;
|
||||
if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value << 8))) {
|
||||
ym_env_cnt = 0;
|
||||
ym_env_step--;
|
||||
if (ym_env_step < 0) {
|
||||
if (ym_env_hold) {
|
||||
if (ym_env_alt)
|
||||
ym_env_att ^= 0x1F;
|
||||
ym_env_holding = 1;
|
||||
ym_env_step = 0;
|
||||
} else {
|
||||
if (ym_env_alt && (ym_env_step & 0x20))
|
||||
ym_env_att ^= 0x1F;
|
||||
ym_env_step &= 0x1F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ym_env_vol = (ym_env_step ^ ym_env_att);
|
||||
|
||||
ym_out = 0;
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (ym_regs[i + 8].value & 0x10) {
|
||||
// Envelope mode
|
||||
ym_out += (ym_ch[i] ? ym_env_vol : 0);
|
||||
} else {
|
||||
// Fixed mode
|
||||
ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;
|
||||
|
||||
if (ym_sample_cnt < 960) {
|
||||
ym_sample_cnt++;
|
||||
} else {
|
||||
ym_sample_cnt = 0;
|
||||
}
|
||||
|
||||
// Loop
|
||||
if (ym_frame == ym_frames) ym_init();
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::update() {
|
||||
if (framebuffer) {
|
||||
// Update 1 out of 2 frames, 60Hz is very laggy
|
||||
if (refresh_cnt & 1) render_video();
|
||||
refresh_cnt++;
|
||||
}
|
||||
|
||||
// Slowly increase volume to avoid jumpscare
|
||||
if (headphone_vol < (70 << 2)) {
|
||||
audio::headphone::set_volume(volume_t::decibel((headphone_vol / 4) - 99) + audio::headphone::volume_range().max);
|
||||
headphone_vol++;
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::ym_init() {
|
||||
uint8_t reg;
|
||||
|
||||
for (reg = 0; reg < 14; reg++) {
|
||||
ym_regs[reg].cnt = 0;
|
||||
// Pick up start pointers for each YM registers RLE blocks
|
||||
ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg * 2) + 3]) << 8) + ymdata_bin[(reg * 2) + 2];
|
||||
ym_regs[reg].same = false; // Useless ?
|
||||
ym_regs[reg].value = 0; // Useless ?
|
||||
}
|
||||
|
||||
ym_frame = 0;
|
||||
}
|
||||
|
||||
AboutView::AboutView(
|
||||
NavigationView& nav) {
|
||||
uint8_t p, c;
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
|
||||
add_children({{
|
||||
&text_title,
|
||||
&text_firmware,
|
||||
&text_cpld_hackrf,
|
||||
&text_cpld_hackrf_status,
|
||||
&button_ok,
|
||||
}});
|
||||
|
||||
if (cpld_hackrf_verify_eeprom()) {
|
||||
text_cpld_hackrf_status.set(" OK");
|
||||
} else {
|
||||
text_cpld_hackrf_status.set("BAD");
|
||||
}
|
||||
|
||||
// Politely ask for about 26kB
|
||||
framebuffer = (ui::Color*)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));
|
||||
|
||||
if (framebuffer) {
|
||||
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
||||
|
||||
// Copy original font palette
|
||||
c = 0;
|
||||
for (p = 0; p < 48; p += 3)
|
||||
paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p + 1], demofont_bin[p + 2]);
|
||||
|
||||
// Increase red in another one
|
||||
c = 0;
|
||||
for (p = 0; p < 48; p += 3)
|
||||
paletteB[c++] = ui::Color(std::min(demofont_bin[p] + 64, 255), demofont_bin[p + 1], demofont_bin[p + 2]);
|
||||
}
|
||||
|
||||
// Init YM synth
|
||||
ym_frames = ((uint16_t)(ymdata_bin[1]) << 8) + ymdata_bin[0];
|
||||
ym_init();
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
if (framebuffer) chHeapFree(framebuffer); // Do NOT forget this
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
AboutView::~AboutView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_ABOUT_H__
|
||||
#define __UI_ABOUT_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class AboutView : public View {
|
||||
public:
|
||||
AboutView(NavigationView& nav);
|
||||
~AboutView();
|
||||
|
||||
void on_show() override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
void ym_init();
|
||||
void update();
|
||||
void render_video();
|
||||
void render_audio();
|
||||
void draw_demoglyph(ui::Point p, char ch, ui::Color* pal);
|
||||
uint16_t debug_cnt = 0;
|
||||
|
||||
typedef struct ymreg_t {
|
||||
uint8_t value;
|
||||
uint8_t cnt;
|
||||
uint16_t ptr;
|
||||
bool same;
|
||||
} ymreg_t;
|
||||
|
||||
uint16_t headphone_vol = 5 << 2;
|
||||
|
||||
ymreg_t ym_regs[14];
|
||||
uint16_t ym_frames;
|
||||
uint16_t ym_frame;
|
||||
uint8_t drum = 0;
|
||||
uint16_t ym_osc_cnt[3];
|
||||
uint32_t ym_rng = 1;
|
||||
uint16_t ym_noise_cnt;
|
||||
uint8_t ym_env_att, ym_env_hold, ym_env_alt, ym_env_holding, ym_env_vol;
|
||||
int8_t ym_env_step;
|
||||
uint16_t ym_env_cnt;
|
||||
uint8_t ym_osc_out[3];
|
||||
uint8_t ym_ch[3];
|
||||
uint8_t ym_out;
|
||||
uint16_t ym_sample_cnt = 0;
|
||||
|
||||
int8_t ym_buffer[1024];
|
||||
|
||||
uint8_t refresh_cnt;
|
||||
ui::Color paletteA[16];
|
||||
ui::Color paletteB[16];
|
||||
ui::Color* framebuffer;
|
||||
uint32_t phase = 0;
|
||||
uint8_t copperbars[5] = {0};
|
||||
uint8_t copperbuffer[72] = {0};
|
||||
|
||||
uint8_t anim_state = 0;
|
||||
uint8_t credits_index = 0;
|
||||
uint16_t credits_timer = 0;
|
||||
|
||||
int16_t ofx = -180, ofy = -24;
|
||||
|
||||
const uint8_t char_map[64] = {0xFF, 27, 46, 0xFF, 0xFF, 0xFF, 28, 45,
|
||||
58, 59, 0xFF, 43, 40, 57, 26, 42,
|
||||
29, 30, 31, 32, 33, 34, 35, 36,
|
||||
37, 38, 41, 0xFF, 0xFF, 0xFF, 0xFF, 44,
|
||||
0xFF, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
const uint8_t copperluma[16] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8};
|
||||
const uint8_t coppercolor[5][3] = {{255, 0, 0},
|
||||
{0, 255, 0},
|
||||
{0, 0, 255},
|
||||
{255, 0, 255},
|
||||
{255, 255, 0}};
|
||||
|
||||
typedef struct credits_t {
|
||||
char role[12];
|
||||
char name[12];
|
||||
bool change;
|
||||
} credits_t;
|
||||
|
||||
// 0123456789A 0123456789A
|
||||
const credits_t credits[10] = {{"GURUS", "J. BOONE", false},
|
||||
{"GURUS", "M. OSSMANN", true},
|
||||
{"BUGS", "FURRTEK", true},
|
||||
{"RDS WAVE", "C. JACQUET", true},
|
||||
{"POCSAG RX", "T. SAILER", false},
|
||||
{"POCSAG RX", "E. OENAL", true},
|
||||
{"XYLOS DATA", "CLX", true},
|
||||
{"GREETS TO", "SIGMOUNTE", false},
|
||||
{"GREETS TO", "WINDYOONA", true},
|
||||
{"THIS MUSIC", "BIG ALEC", true}};
|
||||
|
||||
Text text_title{
|
||||
{100, 32, 40, 16},
|
||||
"About",
|
||||
};
|
||||
|
||||
Text text_firmware{
|
||||
{0, 236, 240, 16},
|
||||
"Version " VERSION_STRING,
|
||||
};
|
||||
|
||||
Text text_cpld_hackrf{
|
||||
{0, 252, 11 * 8, 16},
|
||||
"HackRF CPLD",
|
||||
};
|
||||
|
||||
Text text_cpld_hackrf_status{
|
||||
{240 - 3 * 8, 252, 3 * 8, 16},
|
||||
"???"};
|
||||
|
||||
Button button_ok{
|
||||
{72, 272, 96, 24},
|
||||
"OK"};
|
||||
|
||||
MessageHandlerRegistration message_handler_update{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->update();
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::FIFOSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const FIFOSignalMessage*>(p);
|
||||
if (message->signaltype == 1) {
|
||||
this->render_audio();
|
||||
baseband::set_fifo_data(ym_buffer);
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_ABOUT_H__*/
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "ui_about_simple.hpp"
|
||||
|
||||
#define ROLL_SPEED_FRAME_PER_LINE 60
|
||||
// cuz frame rate of pp screen is probably 60, scroll per sec
|
||||
|
||||
namespace ui {
|
||||
|
||||
// TODO: Generate this automatically from github
|
||||
@@ -12,17 +15,25 @@ const std::string authors_list[] = {
|
||||
"johnelder,jwetzell,nnemanjan00",
|
||||
"N0vaPixel,klockee,gullradriel",
|
||||
"jamesshao8,ITAxReal,rascafr",
|
||||
"mcules,dqs105,strijar",
|
||||
"mcules,dqs105,strijar,fossum",
|
||||
"zhang00963,RedFox-Fr,aldude999",
|
||||
"East2West,fossum,ArjanOnwezen",
|
||||
"vXxOinvizioNxX,teixeluis",
|
||||
"Brumi-2021,texasyojimbo",
|
||||
"East2West,ArjanOnwezen,jLynx",
|
||||
"vXxOinvizioNxX,teixeluis,n0xa",
|
||||
"Brumi-2021,texasyojimbo,zigad",
|
||||
"heurist1,intoxsick,ckuethe",
|
||||
"notpike,jLynx,zigad",
|
||||
"MichalLeonBorsuk,jimilinuxguy",
|
||||
"kallanreed,bernd-herzog",
|
||||
"NotherNgineer,zxkmm,u-foka",
|
||||
"Netro,HTotoo",
|
||||
"notpike,MichalLeonBorsuk",
|
||||
"kallanreed,bernd-herzog,zxkmm",
|
||||
"NotherNgineer,jimilinuxguy",
|
||||
"iNetro,HTotoo,BehleZebub,dc2dc",
|
||||
"arneluehrs,u-foka,Maescool",
|
||||
"nnesetto,formtapez,F33RNI",
|
||||
"rusty-labs,andrej-mk,taskinen",
|
||||
"MatiasFernandez,TQMatvey,KimIV",
|
||||
"RobertoD91,RndmNmbr,OpCode1300",
|
||||
"alex10791,GitClo,GermanAizek",
|
||||
"JimiHFord,jstockdale,aSmig",
|
||||
"KillerTurtleSoftware,Lpd738",
|
||||
"MattLodge,z3r0l1nk,joyel24",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"furrtek,mrmookie,NotPike",
|
||||
@@ -79,10 +90,42 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::on_frame_sync() {
|
||||
if (interacted) return;
|
||||
|
||||
if (frame_sync_count++ % ROLL_SPEED_FRAME_PER_LINE == 0) {
|
||||
const auto current = menu_view.highlighted_index();
|
||||
const auto count = menu_view.item_count();
|
||||
|
||||
if (current < count - 1) {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
interacted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AboutView::on_key(const KeyEvent) {
|
||||
interacted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AboutView::on_encoder(const EncoderEvent) {
|
||||
interacted = true;
|
||||
menu_view.focus();
|
||||
// put focus on last text line to make it more obvious that list is scrollable
|
||||
menu_view.set_highlighted(10);
|
||||
return false;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -14,6 +14,13 @@ class AboutView : public View {
|
||||
std::string title() const override { return "About"; };
|
||||
|
||||
private:
|
||||
virtual bool on_key(const KeyEvent event);
|
||||
virtual bool on_encoder(const EncoderEvent event);
|
||||
virtual bool on_touch(const TouchEvent event);
|
||||
|
||||
bool interacted{false};
|
||||
uint16_t frame_sync_count{0};
|
||||
void on_frame_sync();
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 264},
|
||||
true};
|
||||
@@ -22,6 +29,12 @@ class AboutView : public View {
|
||||
{240 / 3, 270, 240 / 3, 24},
|
||||
"OK",
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_frame_sync();
|
||||
}};
|
||||
};
|
||||
} // namespace ui
|
||||
|
||||
|
||||
@@ -94,21 +94,39 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
record_view.set_sampling_rate(24000);
|
||||
|
||||
options_region.on_change = [this](size_t, int32_t i) {
|
||||
if (i == 0) {
|
||||
field_frequency.on_change = [this](rf::Frequency f) { (void)f; };
|
||||
if (i == 0) { // MAN Manual setting
|
||||
field_frequency.set_value(aprs_rx_freq);
|
||||
} else if (i == 1) { // NA - North America - is also the default
|
||||
field_frequency.set_value(144390000);
|
||||
} else if (i == 1) {
|
||||
} else if (i == 2) { // EUR
|
||||
field_frequency.set_value(144800000);
|
||||
} else if (i == 2) {
|
||||
} else if (i == 3) { // AUS
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 3) {
|
||||
} else if (i == 4) { // NZ
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 4) {
|
||||
} else if (i == 5) { // ISS
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
options_region_id = i;
|
||||
receiver_model.set_target_frequency(field_frequency.value()); // Retune
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
aprs_rx_freq = f;
|
||||
options_region.set_selected_index(0, true);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency.set_step(100);
|
||||
options_region.set_selected_index(0, true);
|
||||
field_frequency.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
aprs_rx_freq = f;
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
// Note: setting the region is also going to sef field_frequency.on_change
|
||||
options_region.set_selected_index(options_region_id, true);
|
||||
|
||||
logger = std::make_unique<APRSLogger>();
|
||||
if (logger)
|
||||
@@ -124,6 +142,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
|
||||
void APRSRxView::on_freqchg(int64_t freq) {
|
||||
field_frequency.set_value(freq);
|
||||
aprs_rx_freq = freq;
|
||||
options_region.set_selected_index(0, true);
|
||||
}
|
||||
|
||||
void APRSRxView::on_packet(const APRSPacketMessage* message) {
|
||||
|
||||
@@ -190,6 +190,8 @@ class APRSRxView : public View {
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
bool reset_console = false;
|
||||
uint8_t options_region_id = 1; // default to North America
|
||||
rf::Frequency aprs_rx_freq{144390000}; // default to North America frequency
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
@@ -198,7 +200,10 @@ class APRSRxView : public View {
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_aprs", app_settings::Mode::RX};
|
||||
"rx_aprs",
|
||||
app_settings::Mode::RX,
|
||||
{{"options_region_id"sv, &options_region_id},
|
||||
{"aprs_rx_freq"sv, &aprs_rx_freq}}};
|
||||
|
||||
uint8_t console_color{0};
|
||||
std::string str_log{""};
|
||||
@@ -220,15 +225,15 @@ class APRSRxView : public View {
|
||||
OptionsField options_region{
|
||||
{0 * 8, 0 * 8},
|
||||
3,
|
||||
{{"NA ", 0},
|
||||
{"EUR", 1},
|
||||
{"AUS", 2},
|
||||
{"NZ ", 3},
|
||||
{"ISS", 4}}};
|
||||
{{"MAN", 0},
|
||||
{"NA ", 1},
|
||||
{"EUR", 2},
|
||||
{"AUS", 3},
|
||||
{"NZ ", 4},
|
||||
{"ISS", 5}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16},
|
||||
nav_};
|
||||
FrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16}};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view{
|
||||
|
||||
@@ -75,7 +75,19 @@ void BattinfoView::update_result() {
|
||||
text_current.hidden(false);
|
||||
text_charge.hidden(false);
|
||||
text_current.set(to_string_dec_int(current) + " mA");
|
||||
text_charge.set(current >= 0 ? "Charging" : "Discharging");
|
||||
if (current >= 25) // when >25mA it is charging in any scenario
|
||||
text_charge.set("Charging");
|
||||
else {
|
||||
if (current > -25) { // between -25 and 25
|
||||
if (voltage > 4060 || percent == 100) { // the voltage is high enough, so it is full, that's why no mA
|
||||
text_charge.set("Full");
|
||||
} else {
|
||||
text_charge.set("Holding"); // not enough voltage, so should charge. maybe the batttery is off, or USB power is not enough
|
||||
}
|
||||
} else {
|
||||
text_charge.set("Discharging"); // less then -25mA, so it is discharging
|
||||
}
|
||||
}
|
||||
labels_opt.hidden(false);
|
||||
|
||||
text_ttef.hidden(false);
|
||||
@@ -103,7 +115,7 @@ void BattinfoView::update_result() {
|
||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
|
||||
text_ttef.hidden(false);
|
||||
float ttef = 0;
|
||||
if (current <= 0) {
|
||||
if (current <= 0) { // we keep this yet
|
||||
ttef = battery::BatteryManagement::get_tte();
|
||||
} else {
|
||||
ttef = battery::BatteryManagement::get_ttf();
|
||||
@@ -133,7 +145,7 @@ void BattinfoView::update_result() {
|
||||
}
|
||||
if (uichg) set_dirty();
|
||||
// to update status bar too, send message in behalf of batt manager
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 0, voltage};
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
#include "ui_debug_max17055.hpp"
|
||||
#include "ui_external_module_view.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -514,6 +515,7 @@ void DebugMenuView::on_populate() {
|
||||
{"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
|
||||
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
|
||||
{"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<ExternalModuleView>(); }},
|
||||
});
|
||||
|
||||
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#define PADDING_LEFT 1
|
||||
#define PADDING_RIGHT 1
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
@@ -154,12 +157,34 @@ void EncodersConfigView::on_show() {
|
||||
}
|
||||
|
||||
void EncodersConfigView::draw_waveform() {
|
||||
// padding reason:
|
||||
// in real world the signal would always start with low level and became low level again after yout turn off the radio;
|
||||
// the waveform_buffer only controls drawing, the real send logic that been sent is controlled by frame_fragments
|
||||
// so just for out of looking things
|
||||
|
||||
size_t length = frame_fragments.length();
|
||||
|
||||
for (size_t n = 0; n < length; n++)
|
||||
waveform_buffer[n] = (frame_fragments[n] == '0') ? 0 : 1;
|
||||
// currently not needed since all the supported OOK protocol wont exceed 550 yet
|
||||
if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) {
|
||||
length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT);
|
||||
}
|
||||
|
||||
waveform.set_length(length);
|
||||
// padding l
|
||||
for (size_t i = 0; i < PADDING_LEFT; i++) {
|
||||
waveform_buffer[i] = 0;
|
||||
}
|
||||
|
||||
// real wf
|
||||
for (size_t n = 0; n < length; n++) {
|
||||
waveform_buffer[n + PADDING_LEFT] = (frame_fragments[n] == '0') ? 0 : 1;
|
||||
}
|
||||
|
||||
// padding r
|
||||
for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) {
|
||||
waveform_buffer[i] = 0;
|
||||
}
|
||||
|
||||
waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT);
|
||||
waveform.set_dirty();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define WAVEFORM_BUFFER_SIZE 550
|
||||
|
||||
using namespace encoders;
|
||||
|
||||
namespace ui {
|
||||
@@ -56,7 +58,7 @@ class EncodersConfigView : public View {
|
||||
std::string frame_fragments = "0";
|
||||
|
||||
private:
|
||||
int16_t waveform_buffer[550];
|
||||
int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE];
|
||||
const encoder_def_t* encoder_def{};
|
||||
|
||||
void draw_waveform();
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_external_module_view.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ui_standalone_view.hpp"
|
||||
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ExternalModuleView::focus() {
|
||||
dummy.focus();
|
||||
}
|
||||
|
||||
void ExternalModuleView::on_tick_second() {
|
||||
i2cdev::I2CDevManager::manual_scan();
|
||||
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
|
||||
if (!dev) {
|
||||
text_header.set("No module connected");
|
||||
text_name.set("");
|
||||
text_version.set("");
|
||||
text_number_apps.set("");
|
||||
text_app1_name.set("");
|
||||
text_app2_name.set("");
|
||||
text_app3_name.set("");
|
||||
text_app4_name.set("");
|
||||
text_app5_name.set("");
|
||||
return;
|
||||
}
|
||||
|
||||
auto device_info = dev->readDeviceInfo();
|
||||
|
||||
if (device_info.has_value() == false) {
|
||||
text_header.set("No module connected");
|
||||
text_name.set("");
|
||||
text_version.set("");
|
||||
text_number_apps.set("");
|
||||
text_app1_name.set("");
|
||||
text_app2_name.set("");
|
||||
text_app3_name.set("");
|
||||
text_app4_name.set("");
|
||||
text_app5_name.set("");
|
||||
return;
|
||||
}
|
||||
|
||||
text_header.set("Module found");
|
||||
|
||||
std::string btnText = (std::string) "Module: " + device_info->module_name;
|
||||
text_name.set(btnText);
|
||||
text_version.set("Version: " + std::to_string(device_info->module_version));
|
||||
text_number_apps.set("No# Apps: " + std::to_string(device_info->application_count));
|
||||
|
||||
for (uint32_t i = 0; i < device_info->application_count && i < 5; i++) {
|
||||
auto appInfo = dev->getStandaloneAppInfo(i);
|
||||
if (appInfo.has_value() == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name;
|
||||
|
||||
switch (appInfo->menu_location) {
|
||||
case app_location_t::UTILITIES:
|
||||
btnText += " (Utilities)";
|
||||
break;
|
||||
case app_location_t::RX:
|
||||
btnText += " (RX)";
|
||||
break;
|
||||
case app_location_t::TX:
|
||||
btnText += " (TX)";
|
||||
break;
|
||||
case app_location_t::DEBUG:
|
||||
btnText += " (Debug)";
|
||||
break;
|
||||
case app_location_t::HOME:
|
||||
btnText += " (Home)";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
text_app1_name.set(btnText);
|
||||
break;
|
||||
case 1:
|
||||
text_app2_name.set(btnText);
|
||||
break;
|
||||
case 2:
|
||||
text_app3_name.set(btnText);
|
||||
break;
|
||||
case 3:
|
||||
text_app4_name.set(btnText);
|
||||
break;
|
||||
case 4:
|
||||
text_app5_name.set(btnText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_EXTERNAL_MODULE_VIEW_H
|
||||
#define __UI_EXTERNAL_MODULE_VIEW_H
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "rffc507x.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "memory_map.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ExternalModuleView : public View {
|
||||
public:
|
||||
ExternalModuleView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_header,
|
||||
&text_name,
|
||||
&text_version,
|
||||
&text_number_apps,
|
||||
&text_app1_name,
|
||||
&text_app2_name,
|
||||
&text_app3_name,
|
||||
&text_app4_name,
|
||||
&text_app5_name,
|
||||
&dummy});
|
||||
|
||||
text_header.set("No module connected");
|
||||
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
~ExternalModuleView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
}
|
||||
|
||||
std::string title() const override { return "Ext Module"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Text text_header{{16, 16, 208, 16}};
|
||||
Text text_name{{24, 32, 200, 16}};
|
||||
Text text_version{{24, 48, 200, 16}};
|
||||
Text text_number_apps{{24, 64, 200, 16}};
|
||||
|
||||
Text text_app1_name{{24, 96, 200, 16}};
|
||||
Text text_app2_name{{24, 112, 200, 16}};
|
||||
Text text_app3_name{{24, 128, 200, 16}};
|
||||
Text text_app4_name{{24, 144, 200, 16}};
|
||||
Text text_app5_name{{24, 160, 200, 16}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
void on_tick_second();
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_fsk_rx.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
|
||||
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
|
||||
|
||||
// // Raw hex dump of all the codewords
|
||||
// for (size_t c = 0; c < 16; c++)
|
||||
// entry += to_string_hex(packet[c], 8) + " ";
|
||||
|
||||
log_file.write_entry(data + entry);
|
||||
}
|
||||
|
||||
void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
|
||||
log_file.write_entry(timestamp, text);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Console View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
|
||||
: View(parent_rect), nav_{nav} {
|
||||
add_child(&console);
|
||||
};
|
||||
|
||||
void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
|
||||
if (is_data) {
|
||||
console.write(to_string_dec_uint(value) + " ");
|
||||
}
|
||||
}
|
||||
|
||||
void FskRxAppConsoleView::on_show() {
|
||||
hidden(false);
|
||||
}
|
||||
|
||||
void FskRxAppConsoleView::on_hide() {
|
||||
hidden(true);
|
||||
}
|
||||
|
||||
FskRxAppConsoleView::~FskRxAppConsoleView() {
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Spectrum View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
|
||||
: View(parent_rect), nav_{nav} {
|
||||
add_child(&waterfall);
|
||||
hidden(true);
|
||||
}
|
||||
|
||||
FskRxAppView::~FskRxAppView() {
|
||||
}
|
||||
|
||||
void FskRxAppView::focus() {
|
||||
}
|
||||
|
||||
void FskRxAppView::on_show() {
|
||||
hidden(false);
|
||||
waterfall.start();
|
||||
}
|
||||
|
||||
void FskRxAppView::on_hide() {
|
||||
hidden(true);
|
||||
waterfall.stop();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Base View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskxRxMainView::FskxRxMainView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&tab_view,
|
||||
&view_data,
|
||||
&view_stream,
|
||||
&labels,
|
||||
&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&deviation_frequency,
|
||||
&record_view});
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_fskrx);
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
deviation_frequency.on_change = [this](rf::Frequency f) {
|
||||
refresh_ui(f);
|
||||
};
|
||||
|
||||
// Set initial sampling rate
|
||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
||||
record_view.set_sampling_rate(initial_deviation * 2);
|
||||
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
deviation_frequency.set_value(initial_deviation);
|
||||
|
||||
logger.append(logs_dir / u"FSKRX.TXT");
|
||||
|
||||
baseband::set_fsk(initial_deviation);
|
||||
|
||||
audio::output::start();
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
|
||||
if (logging()) {
|
||||
logger.log_decoded(timestamp, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
|
||||
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
||||
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
||||
auto sample_rate = deviationHz * 2;
|
||||
|
||||
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
||||
|
||||
if (!view_stream.hidden()) {
|
||||
view_stream.waterfall.stop();
|
||||
}
|
||||
|
||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||
// NB: record_view is what actually updates proc_capture baseband settings.
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
||||
|
||||
// Update the radio model with the actual sampling rate.
|
||||
receiver_model.set_sampling_rate(actual_sample_rate);
|
||||
|
||||
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
|
||||
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||
|
||||
if (!view_stream.hidden()) {
|
||||
view_stream.waterfall.start();
|
||||
}
|
||||
}
|
||||
|
||||
void FskxRxMainView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
view_stream.waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
FskxRxMainView::~FskxRxMainView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
} /* namespace ui */
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_FSK_RX_H__
|
||||
#define __UI_FSK_RX_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "pocsag_app.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
class FskRxLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void log_raw_data(const std::string& data, const uint32_t frequency);
|
||||
void log_decoded(Timestamp timestamp, const std::string& text);
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
class FskRxAppConsoleView : public View {
|
||||
public:
|
||||
FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
|
||||
~FskRxAppConsoleView();
|
||||
|
||||
std::string title() const override { return "FSK RX Data"; };
|
||||
|
||||
void on_packet(uint32_t value, bool is_data);
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Console console{
|
||||
{0, 0, 240, 224}};
|
||||
};
|
||||
|
||||
class FskRxAppView : public View {
|
||||
public:
|
||||
FskRxAppView(NavigationView& nav, Rect parent_rect);
|
||||
~FskRxAppView();
|
||||
|
||||
void focus() override;
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
std::string title() const override { return "FSK RX Stream"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
};
|
||||
|
||||
class FskxRxMainView : public View {
|
||||
public:
|
||||
FskxRxMainView(NavigationView& nav);
|
||||
~FskxRxMainView();
|
||||
|
||||
void focus() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
std::string title() const override { return "FSK RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 902'075'000;
|
||||
static constexpr ui::Dim header_height = (5 * 16);
|
||||
|
||||
uint32_t initial_deviation{3750};
|
||||
|
||||
bool logging() const { return false; };
|
||||
bool logging_raw() const { return false; };
|
||||
|
||||
NavigationView& nav_;
|
||||
Rect view_rect = {0, header_height, 240, 224};
|
||||
|
||||
FskRxAppView view_stream{nav_, view_rect};
|
||||
FskRxAppConsoleView view_data{nav_, view_rect};
|
||||
|
||||
TabView tab_view{
|
||||
{"Data", Theme::getInstance()->fg_yellow->foreground, &view_data},
|
||||
{"Stream", Theme::getInstance()->fg_cyan->foreground, &view_stream}};
|
||||
|
||||
void refresh_ui(rf::Frequency f);
|
||||
void on_packet(uint32_t value, bool is_data);
|
||||
void handle_decoded(Timestamp timestamp, const std::string& prefix);
|
||||
|
||||
uint32_t last_address = 0;
|
||||
FskRxLogger logger{};
|
||||
uint16_t packet_count = 0;
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 4 * 8},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{11 * 8, 2 * 16}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{13 * 8, 2 * 16}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{16 * 8, 2 * 16}};
|
||||
|
||||
RSSI rssi{
|
||||
{19 * 8 - 4, 35, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{19 * 8 - 4, 40, 6 * 8, 4}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Deviation:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
FrequencyField deviation_frequency{
|
||||
{10 * 8, 3 * 16},
|
||||
{3750, 500000},
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view{
|
||||
{0 * 8, 4 * 16, 30 * 8, 1 * 16},
|
||||
u"FSKRX_????.C16",
|
||||
u"FSKRX",
|
||||
RecordView::FileType::RawS16,
|
||||
16384,
|
||||
3};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const AFSKDataMessage*>(p);
|
||||
this->view_data.on_packet(message->value, message->is_data);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__POCSAG_APP_H__*/
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_numbers.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable)
|
||||
|
||||
void NumbersStationView::focus() {
|
||||
if (file_error)
|
||||
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT);
|
||||
else
|
||||
button_exit.focus();
|
||||
}
|
||||
|
||||
NumbersStationView::~NumbersStationView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
NumbersStationView::wav_file_t* NumbersStationView::get_wav(uint32_t index) {
|
||||
return ¤t_voice->available_wavs[index];
|
||||
}
|
||||
|
||||
void NumbersStationView::prepare_audio() {
|
||||
uint8_t code;
|
||||
wav_file_t* wav_file;
|
||||
|
||||
if (sample_counter >= sample_length) {
|
||||
if (segment == ANNOUNCE) {
|
||||
if (!announce_loop) {
|
||||
code_index = 0;
|
||||
segment = MESSAGE;
|
||||
} else {
|
||||
wav_file = get_wav(11);
|
||||
reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav");
|
||||
sample_length = wav_file->length;
|
||||
announce_loop--;
|
||||
}
|
||||
}
|
||||
|
||||
if (segment == MESSAGE) {
|
||||
if (code_index == 25) {
|
||||
transmitter_model.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
code = symfield_code.get_offset(code_index);
|
||||
|
||||
if (code >= 10) {
|
||||
memset(audio_buffer, 0, 1024);
|
||||
if (code == 10) {
|
||||
pause = 11025; // p: 0.25s @ 44100Hz
|
||||
} else if (code == 11) {
|
||||
pause = 33075; // P: 0.75s @ 44100Hz
|
||||
} else if (code == 12) {
|
||||
transmitter_model.disable();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
wav_file = get_wav(code);
|
||||
reader->open(current_voice->dir + file_names[code].name + ".wav");
|
||||
sample_length = wav_file->length;
|
||||
}
|
||||
code_index++;
|
||||
}
|
||||
sample_counter = 0;
|
||||
}
|
||||
|
||||
if (!pause) {
|
||||
auto bytes_read = reader->read(audio_buffer, 1024).value();
|
||||
|
||||
// Unsigned to signed, pretty stupid :/
|
||||
for (size_t n = 0; n < bytes_read; n++)
|
||||
audio_buffer[n] -= 0x80;
|
||||
for (size_t n = bytes_read; n < 1024; n++)
|
||||
audio_buffer[n] = 0;
|
||||
|
||||
sample_counter += 1024;
|
||||
} else {
|
||||
if (pause >= 1024) {
|
||||
pause -= 1024;
|
||||
} else {
|
||||
sample_counter = sample_length;
|
||||
pause = 0;
|
||||
}
|
||||
}
|
||||
|
||||
baseband::set_fifo_data(audio_buffer);
|
||||
}
|
||||
|
||||
void NumbersStationView::start_tx() {
|
||||
// sample_length = sound_sizes[10]; // Announce
|
||||
sample_counter = sample_length;
|
||||
|
||||
code_index = 0;
|
||||
announce_loop = 2;
|
||||
segment = ANNOUNCE;
|
||||
|
||||
prepare_audio();
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_audiotx_data(
|
||||
(1536000 / 44100) - 1, // TODO: Read wav file's samplerate
|
||||
12000,
|
||||
1,
|
||||
false,
|
||||
0);
|
||||
}
|
||||
|
||||
void NumbersStationView::on_tick_second() {
|
||||
armed_blink = not armed_blink;
|
||||
|
||||
if (armed_blink)
|
||||
check_armed.set_style(Theme::getInstance()->fg_red);
|
||||
else
|
||||
check_armed.set_style(&style());
|
||||
|
||||
check_armed.set_dirty();
|
||||
}
|
||||
|
||||
void NumbersStationView::on_voice_changed(size_t index) {
|
||||
std::string code_list;
|
||||
|
||||
for (const auto& wavs : voices[index].available_wavs)
|
||||
code_list += wavs.code;
|
||||
|
||||
symfield_code.set_symbol_list(code_list);
|
||||
current_voice = &voices[index];
|
||||
}
|
||||
|
||||
bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) {
|
||||
if (reader->open("/numbers/" + dir + "/" + file)) {
|
||||
// Check format (mono, 8 bits)
|
||||
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
NumbersStationView::NumbersStationView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
std::vector<std::filesystem::path> directory_list;
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
options_t voice_options;
|
||||
voice_t temp_voice{};
|
||||
bool valid;
|
||||
uint32_t c;
|
||||
// uint8_t y, m, d, dayofweek;
|
||||
|
||||
reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
// Search for valid voice directories
|
||||
directory_list = scan_root_directories("/numbers");
|
||||
if (!directory_list.size()) {
|
||||
file_error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& dir : directory_list) {
|
||||
c = 0;
|
||||
for (const auto& file_name : file_names) {
|
||||
valid = check_wav_validity(dir.string(), file_name.name + ".wav");
|
||||
if ((!valid) && (file_name.required)) {
|
||||
temp_voice.available_wavs.clear();
|
||||
break; // Invalid required file means invalid voice
|
||||
} else if (valid) {
|
||||
temp_voice.available_wavs.push_back({file_name.code, c++, 0, 0}); // TODO: Needs length and samplerate
|
||||
}
|
||||
}
|
||||
if (!temp_voice.available_wavs.empty()) {
|
||||
// Voice can be used, are there accent files ?
|
||||
c = 0;
|
||||
for (const auto& file_name : file_names) {
|
||||
valid = check_wav_validity(dir.string(), file_name.name + "a.wav");
|
||||
if ((!valid) && (file_name.required)) {
|
||||
c = 0;
|
||||
break; // Invalid required file means accents can't be used
|
||||
} else if (valid) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
temp_voice.accent = c ? true : false;
|
||||
temp_voice.dir = dir.string();
|
||||
|
||||
voices.push_back(temp_voice);
|
||||
}
|
||||
}
|
||||
|
||||
if (voices.empty()) {
|
||||
file_error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
|
||||
add_children({&labels,
|
||||
&symfield_code,
|
||||
&check_armed,
|
||||
&options_voices,
|
||||
&text_voice_flags,
|
||||
//&button_tx_now,
|
||||
&button_exit});
|
||||
|
||||
for (const auto& voice : voices)
|
||||
voice_options.emplace_back(voice.dir.substr(0, 4), 0);
|
||||
|
||||
options_voices.set_options(voice_options);
|
||||
options_voices.on_change = [this](size_t i, int32_t) {
|
||||
this->on_voice_changed(i);
|
||||
};
|
||||
options_voices.set_selected_index(0);
|
||||
|
||||
check_armed.set_value(false);
|
||||
|
||||
check_armed.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) {
|
||||
armed_blink = false;
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
this->on_tick_second();
|
||||
};
|
||||
} else {
|
||||
check_armed.set_style(&style());
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
}
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
symfield_code.set_offset(0, 10);
|
||||
symfield_code.set_offset(1, 3);
|
||||
symfield_code.set_offset(2, 4);
|
||||
symfield_code.set_offset(3, 11);
|
||||
symfield_code.set_offset(4, 6);
|
||||
symfield_code.set_offset(5, 1);
|
||||
symfield_code.set_offset(6, 9);
|
||||
symfield_code.set_offset(7, 7);
|
||||
symfield_code.set_offset(8, 8);
|
||||
symfield_code.set_offset(9, 0);
|
||||
symfield_code.set_offset(10, 12); // End
|
||||
|
||||
/*
|
||||
dayofweek = rtc_time::current_day_of_week();
|
||||
text_title.set(day_of_week[dayofweek]);
|
||||
*/
|
||||
|
||||
button_exit.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_NUMBERS_H__
|
||||
#define __UI_NUMBERS_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "clock_manager.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "message.hpp"
|
||||
#include "file.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NumbersStationView : public View {
|
||||
public:
|
||||
NumbersStationView(NavigationView& nav);
|
||||
~NumbersStationView();
|
||||
|
||||
NumbersStationView(const NumbersStationView&) = delete;
|
||||
NumbersStationView(NumbersStationView&&) = delete;
|
||||
NumbersStationView& operator=(const NumbersStationView&) = delete;
|
||||
NumbersStationView& operator=(NumbersStationView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Station"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
// Sequencing state machine
|
||||
enum segments {
|
||||
IDLE = 0,
|
||||
ANNOUNCE,
|
||||
MESSAGE,
|
||||
SIGNOFF
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char code;
|
||||
uint32_t index;
|
||||
uint32_t length;
|
||||
uint32_t samplerate;
|
||||
} wav_file_t;
|
||||
|
||||
struct voice_t {
|
||||
std::string dir;
|
||||
std::vector<wav_file_t> available_wavs;
|
||||
bool accent;
|
||||
};
|
||||
|
||||
std::vector<voice_t> voices{};
|
||||
voice_t* current_voice{};
|
||||
|
||||
struct wav_file_list_t {
|
||||
std::string name;
|
||||
bool required;
|
||||
char code;
|
||||
};
|
||||
|
||||
const std::vector<wav_file_list_t> file_names = {
|
||||
{"0", true, '0'},
|
||||
{"1", true, '1'},
|
||||
{"2", true, '2'},
|
||||
{"3", true, '3'},
|
||||
{"4", true, '4'},
|
||||
{"5", true, '5'},
|
||||
{"6", true, '6'},
|
||||
{"7", true, '7'},
|
||||
{"8", true, '8'},
|
||||
{"9", true, '9'},
|
||||
{"announce", false, 'A'}};
|
||||
|
||||
segments segment{IDLE};
|
||||
bool armed{false};
|
||||
bool file_error{false};
|
||||
|
||||
// const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
|
||||
// const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
|
||||
|
||||
std::unique_ptr<WAVFileReader> reader{};
|
||||
|
||||
uint8_t code_index{0}, announce_loop{0};
|
||||
uint32_t sample_counter{0};
|
||||
uint32_t sample_length{0};
|
||||
int8_t audio_buffer[1024]{};
|
||||
uint32_t pause{0};
|
||||
bool armed_blink{false};
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
wav_file_t* get_wav(uint32_t index);
|
||||
bool check_wav_validity(const std::string dir, const std::string file);
|
||||
void on_voice_changed(size_t index);
|
||||
void on_tick_second();
|
||||
void prepare_audio();
|
||||
void start_tx();
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 5 * 8}, "Voice: Flags:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 8 * 8}, "Code:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField options_voices{
|
||||
{8 * 8, 1 * 8},
|
||||
4,
|
||||
{}};
|
||||
Text text_voice_flags{
|
||||
{19 * 8, 1 * 8, 2 * 8, 16},
|
||||
""};
|
||||
|
||||
SymField symfield_code{
|
||||
{1 * 8, 10 * 8},
|
||||
25,
|
||||
SymField::Type::Custom};
|
||||
|
||||
Checkbox check_armed{
|
||||
{2 * 8, 13 * 16},
|
||||
5,
|
||||
"Armed"};
|
||||
|
||||
/*
|
||||
Button button_tx_now {
|
||||
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
||||
"TX now"};
|
||||
*/
|
||||
|
||||
Button button_exit{
|
||||
{21 * 8, 16 * 16, 64, 32},
|
||||
"Exit"};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->prepare_audio();
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_NUMBERS_H__*/
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_nuoptix.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "portapack.hpp"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void NuoptixView::focus() {
|
||||
number_timecode.focus();
|
||||
}
|
||||
|
||||
NuoptixView::~NuoptixView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void NuoptixView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
if (done)
|
||||
transmit(false);
|
||||
else
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void NuoptixView::transmit(bool setup) {
|
||||
uint8_t mod, tone_code;
|
||||
uint8_t c;
|
||||
uint8_t dtmf_message[6];
|
||||
rtc::RTC datetime;
|
||||
|
||||
if (!tx_mode) {
|
||||
transmitter_model.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tx_mode == IMPROVISE)
|
||||
timecode = lfsr_iterate(timecode) % 1999; // Could be 9999 but that would be one long audio track !
|
||||
|
||||
if (setup) {
|
||||
progressbar.set_max(6 * 2);
|
||||
|
||||
if (tx_mode == IMPROVISE) {
|
||||
// Seed from RTC
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
timecode = datetime.day() + datetime.second();
|
||||
} else {
|
||||
timecode = number_timecode.value();
|
||||
}
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.enable();
|
||||
|
||||
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
|
||||
dtmf_message[1] = 'A'; // "Restart" method #1
|
||||
} else {
|
||||
dtmf_message[0] = '#';
|
||||
dtmf_message[1] = (timecode / 1000) % 10;
|
||||
chThdSleepMilliseconds(92); // 141-49ms
|
||||
number_timecode.set_value(timecode);
|
||||
}
|
||||
|
||||
progressbar.set_value(0);
|
||||
|
||||
dtmf_message[2] = (timecode / 100) % 10;
|
||||
dtmf_message[3] = (timecode / 10) % 10;
|
||||
dtmf_message[4] = timecode % 10;
|
||||
|
||||
mod = 0;
|
||||
for (c = 1; c < 5; c++)
|
||||
if (dtmf_message[c] <= 9)
|
||||
mod += dtmf_message[c];
|
||||
|
||||
mod = 10 - (mod % 10);
|
||||
if (mod == 10) mod = 0; // Is this right ?
|
||||
|
||||
text_mod.set("Mod: " + to_string_dec_uint(mod));
|
||||
|
||||
dtmf_message[5] = mod;
|
||||
|
||||
for (c = 0; c < 6; c++) {
|
||||
tone_code = dtmf_message[c];
|
||||
|
||||
if (tone_code == 'A')
|
||||
tone_code = 10;
|
||||
else if (tone_code == 'B')
|
||||
tone_code = 11;
|
||||
else if (tone_code == 'C')
|
||||
tone_code = 12;
|
||||
else if (tone_code == 'D')
|
||||
tone_code = 13;
|
||||
else if (tone_code == '#')
|
||||
tone_code = 14;
|
||||
else if (tone_code == '*')
|
||||
tone_code = 15;
|
||||
|
||||
shared_memory.bb_data.tones_data.message[c * 2] = tone_code;
|
||||
shared_memory.bb_data.tones_data.message[c * 2 + 1] = 0xFF; // Silence
|
||||
}
|
||||
|
||||
for (c = 0; c < 16; c++) {
|
||||
baseband::set_tone(c * 2, dtmf_deltas[c][0], NUOPTIX_TONE_LENGTH);
|
||||
baseband::set_tone(c * 2 + 1, dtmf_deltas[c][1], NUOPTIX_TONE_LENGTH);
|
||||
}
|
||||
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
|
||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
|
||||
|
||||
timecode++;
|
||||
}
|
||||
|
||||
NuoptixView::NuoptixView(
|
||||
NavigationView& nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_tones);
|
||||
|
||||
add_children({&number_timecode,
|
||||
&text_timecode,
|
||||
&text_mod,
|
||||
&progressbar,
|
||||
&tx_view});
|
||||
|
||||
number_timecode.set_value(1);
|
||||
|
||||
tx_view.on_edit_frequency = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
tx_view.set_transmitting(true);
|
||||
tx_mode = NORMAL;
|
||||
transmit(true);
|
||||
};
|
||||
|
||||
tx_view.on_stop = [this]() {
|
||||
tx_view.set_transmitting(false);
|
||||
tx_mode = IDLE;
|
||||
};
|
||||
|
||||
/*button_impro.on_select = [this](Button&){
|
||||
if (tx_mode == IMPROVISE) {
|
||||
tx_mode = IDLE;
|
||||
button_impro.set_text("IMPROVISE");
|
||||
} else if (tx_mode == IDLE) {
|
||||
tx_mode = IMPROVISE;
|
||||
button_impro.set_text("STOP");
|
||||
transmit(true);
|
||||
}
|
||||
};*/
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_NUOPTIX_H__
|
||||
#define __UI_NUOPTIX_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "tonesets.hpp"
|
||||
#include "message.hpp"
|
||||
#include "volume.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
#define NUOPTIX_TONE_LENGTH ((TONES_SAMPLERATE * 0.049) - 1) // 49ms
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NuoptixView : public View {
|
||||
public:
|
||||
NuoptixView(NavigationView& nav);
|
||||
~NuoptixView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Nuoptix sync"; };
|
||||
|
||||
private:
|
||||
enum tx_modes {
|
||||
IDLE = 0,
|
||||
NORMAL,
|
||||
IMPROVISE
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
tx_modes tx_mode{IDLE};
|
||||
|
||||
void transmit(bool setup);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
uint32_t timecode{0};
|
||||
|
||||
Text text_timecode{
|
||||
{10 * 8, 2 * 16, 9 * 8, 16},
|
||||
"Timecode:"};
|
||||
|
||||
NumberField number_timecode{
|
||||
{13 * 8, 3 * 16},
|
||||
4,
|
||||
{1, 9999},
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Text text_mod{
|
||||
{10 * 8, 5 * 16, 6 * 8, 16},
|
||||
"Mod: "};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{16, 14 * 16, 208, 16}};
|
||||
|
||||
/*Button button_impro {
|
||||
{ 64, 184, 112, 40 },
|
||||
"IMPROVISE"
|
||||
};*/
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
10000,
|
||||
15};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress, message.done);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_NUOPTIX_H__*/
|
||||
@@ -33,6 +33,8 @@ using namespace pocsag;
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define MAX_POCSAG_LENGTH 80
|
||||
|
||||
void POCSAGTXView::focus() {
|
||||
field_address.focus();
|
||||
}
|
||||
@@ -57,6 +59,7 @@ void POCSAGTXView::on_remote(const PocsagTosendMessage data) {
|
||||
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
|
||||
field_address.set_value(data.addr);
|
||||
message = (char*)data.msg;
|
||||
buffer = message;
|
||||
text_message.set(message);
|
||||
options_bitrate.dirty();
|
||||
options_type.dirty();
|
||||
@@ -105,9 +108,9 @@ bool POCSAGTXView::start_tx() {
|
||||
|
||||
progressbar.set_max(total_frames);
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_tx_gain(40);
|
||||
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
|
||||
// transmitter_model.set_rf_amp(true);
|
||||
// transmitter_model.set_tx_gain(40);
|
||||
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
|
||||
transmitter_model.enable();
|
||||
|
||||
@@ -139,11 +142,18 @@ bool POCSAGTXView::start_tx() {
|
||||
|
||||
void POCSAGTXView::paint(Painter&) {
|
||||
message = buffer;
|
||||
text_message.set(message);
|
||||
text_message.set(message); // the whole message, but it may not fit.
|
||||
if (message.length() > 30 && message.length() <= 60) {
|
||||
text_message_l2.set(message.substr(29)); // remaining to 2nd line
|
||||
} else if (message.length() > 60) {
|
||||
text_message_l2.set(message.substr(29, 27) + "...");
|
||||
} else {
|
||||
text_message_l2.set("");
|
||||
}
|
||||
}
|
||||
|
||||
void POCSAGTXView::on_set_text(NavigationView& nav) {
|
||||
text_prompt(nav, buffer, 30);
|
||||
text_prompt(nav, buffer, MAX_POCSAG_LENGTH);
|
||||
}
|
||||
|
||||
POCSAGTXView::POCSAGTXView(
|
||||
@@ -158,6 +168,7 @@ POCSAGTXView::POCSAGTXView(
|
||||
&options_function,
|
||||
&options_phase,
|
||||
&text_message,
|
||||
&text_message_l2,
|
||||
&button_message,
|
||||
&progressbar,
|
||||
&tx_view});
|
||||
|
||||
@@ -122,15 +122,18 @@ class POCSAGTXView : public View {
|
||||
}};
|
||||
|
||||
Text text_message{
|
||||
{0 * 8, 16 * 8, 16 * 8, 16},
|
||||
{0 * 8, 16 * 8, 30 * 8, 16},
|
||||
""};
|
||||
Text text_message_l2{
|
||||
{0 * 8, 18 * 8, 30 * 8, 16},
|
||||
""};
|
||||
|
||||
Button button_message{
|
||||
{0 * 8, 18 * 8, 14 * 8, 32},
|
||||
{0 * 8, 20 * 8, 14 * 8, 32},
|
||||
"Set message"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{16, 200, 208, 16}};
|
||||
{16, 210, 208, 16}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "replay_app.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_script.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ScriptView::on_frequency_select() {
|
||||
// button_edit_freq.focus();
|
||||
}
|
||||
|
||||
void ScriptView::on_edit_freq(rf::Frequency f) {
|
||||
(void)f;
|
||||
// frequencies[menu_view.highlighted()].value = f;
|
||||
setup_list();
|
||||
}
|
||||
|
||||
void ScriptView::on_edit_desc(NavigationView& nav) {
|
||||
(void)nav;
|
||||
}
|
||||
|
||||
void ScriptView::on_delete() {
|
||||
// frequencies.erase(frequencies.begin() + menu_view.highlighted());
|
||||
setup_list();
|
||||
}
|
||||
|
||||
void ScriptView::setup_list() {
|
||||
// size_t n;
|
||||
|
||||
menu_view.clear();
|
||||
|
||||
/*for (n = 0; n < frequencies.size(); n++) {
|
||||
menu_view.add_item({ freqman_item_string(frequencies[n]), Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ on_frequency_select(); } });
|
||||
}*/
|
||||
|
||||
menu_view.set_parent_rect({0, 0, 240, 168});
|
||||
menu_view.set_highlighted(menu_view.highlighted()); // Refresh
|
||||
}
|
||||
|
||||
void ScriptView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
ScriptView::ScriptView(
|
||||
NavigationView& nav) {
|
||||
add_children({&menu_view,
|
||||
&text_edit,
|
||||
&button_edit_freq,
|
||||
&button_edit_desc,
|
||||
&button_del,
|
||||
&button_exit});
|
||||
|
||||
setup_list();
|
||||
|
||||
button_edit_freq.on_select = [this, &nav](Button&) {
|
||||
/*auto new_view = nav.push<FrequencyKeypadView>(frequencies[menu_view.highlighted()].value);
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
on_edit_freq(f);
|
||||
};*/
|
||||
};
|
||||
|
||||
button_edit_desc.on_select = [this, &nav](Button&) {
|
||||
on_edit_desc(nav);
|
||||
};
|
||||
|
||||
button_del.on_select = [this, &nav](Button&) {
|
||||
nav.push<ModalMessageView>("Confirm", "Are you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
on_delete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
enum script_keyword {
|
||||
STOP = 0,
|
||||
WAIT_N,
|
||||
WAIT_RTC,
|
||||
IF,
|
||||
LOOP,
|
||||
END,
|
||||
TX,
|
||||
RX
|
||||
};
|
||||
|
||||
struct script_line {
|
||||
script_keyword keyword;
|
||||
};
|
||||
|
||||
class ScriptView : public View {
|
||||
public:
|
||||
ScriptView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Script editor"; };
|
||||
|
||||
private:
|
||||
void on_frequency_select();
|
||||
void on_edit_freq(rf::Frequency f);
|
||||
void on_edit_desc(NavigationView& nav);
|
||||
void on_delete();
|
||||
void setup_list();
|
||||
|
||||
std::vector<script_line> script{};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 168},
|
||||
true};
|
||||
|
||||
Text text_edit{
|
||||
{16, 194, 5 * 8, 16},
|
||||
"Edit:"};
|
||||
Button button_edit_freq{
|
||||
{16, 194 + 16, 88, 32},
|
||||
"Frequency"};
|
||||
Button button_edit_desc{
|
||||
{16, 194 + 16 + 34, 88, 32},
|
||||
"Description"};
|
||||
Button button_del{
|
||||
{160, 192, 72, 64},
|
||||
"Delete"};
|
||||
|
||||
Button button_exit{
|
||||
{160, 264, 72, 32},
|
||||
"Exit"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -324,7 +324,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_fake_brightness,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
@@ -362,7 +361,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
||||
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
||||
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
@@ -391,7 +389,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
||||
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
||||
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
@@ -780,20 +777,13 @@ void SetConfigModeView::focus() {
|
||||
/* SetDisplayView ************************************/
|
||||
|
||||
SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
add_children({&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_invert_switch,
|
||||
&checkbox_brightness_switch});
|
||||
&checkbox_invert_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());
|
||||
|
||||
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());
|
||||
@@ -801,13 +791,6 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
// only enable invert OR fake brightness
|
||||
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_brightness_switch.set_value(false);
|
||||
};
|
||||
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_invert_switch.set_value(false);
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
@@ -818,6 +801,107 @@ void SetDisplayView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetTouchscreenSensitivityView ************************************/
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold = 1023 / sensitive
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
*/
|
||||
SetTouchscreenThresholdView::SetTouchscreenThresholdView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_threshold,
|
||||
&button_autodetect,
|
||||
&button_reset,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&text_hint,
|
||||
&text_wait_timer});
|
||||
|
||||
set_dirty();
|
||||
org_threshold = portapack::touch_threshold;
|
||||
field_threshold.set_value(pmem::touchscreen_threshold());
|
||||
text_hint.set_style(Theme::getInstance()->error_dark);
|
||||
text_hint.hidden(true);
|
||||
text_wait_timer.set_style(Theme::getInstance()->error_dark);
|
||||
text_wait_timer.hidden(true);
|
||||
// clang-format off
|
||||
button_autodetect.on_select = [this, &nav](Button&) {
|
||||
nav.display_modal("NOTICE",
|
||||
"Now on don't touch screen;\n"
|
||||
"Use arrow keys to operate.\n"
|
||||
"Follow instructions.\n"
|
||||
"Press YES to continue",
|
||||
YESNO, [this, &nav](bool choice) {
|
||||
if (choice){
|
||||
time_start_auto_detect = chTimeNow();
|
||||
text_hint.hidden(false);
|
||||
text_wait_timer.hidden(false);
|
||||
text_wait_timer.set("ETA " + to_string_dec_uint(10) + "s");
|
||||
in_auto_detect = true;
|
||||
field_threshold.set_value(1);
|
||||
portapack::touch_threshold = 1;
|
||||
set_dirty(); } }, TRUE);
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
button_reset.on_select = [this](Button&) {
|
||||
field_threshold.set_value(32);
|
||||
portapack::touch_threshold = 32;
|
||||
};
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_touchscreen_threshold(field_threshold.value());
|
||||
portapack::touch_threshold = field_threshold.value();
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
portapack::touch_threshold = org_threshold;
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetTouchscreenThresholdView::focus() {
|
||||
button_autodetect.focus();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void SetTouchscreenThresholdView::on_frame_sync() {
|
||||
if (!in_auto_detect) return;
|
||||
uint32_t time_now = chTimeNow();
|
||||
int32_t time_diff = time_now - time_start_auto_detect;
|
||||
text_wait_timer.set("ETA " + to_string_dec_uint((10 - time_diff / 1000) <= 0 ? 0 : 10 - time_diff / 1000) + "s");
|
||||
if (time_diff >= 10001 && !auto_detect_succeed_consumed) { // 10s
|
||||
in_auto_detect = false;
|
||||
text_wait_timer.hidden(true);
|
||||
text_hint.set("OK, press save and reboot");
|
||||
portapack::touch_threshold = org_threshold;
|
||||
pmem::set_touchscreen_threshold(org_threshold);
|
||||
set_dirty();
|
||||
auto_detect_succeed_consumed = true;
|
||||
button_save.focus();
|
||||
return;
|
||||
}
|
||||
if (get_touch_frame().touch) {
|
||||
if (in_auto_detect) {
|
||||
uint16_t sen = field_threshold.value();
|
||||
sen++;
|
||||
portapack::touch_threshold = sen;
|
||||
field_threshold.set_value(sen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetTouchscreenThresholdView::~SetTouchscreenThresholdView() {
|
||||
// it seems that sometimes in the msg handler func it would enter the condi that not possible to entered,
|
||||
// so added this workaround.
|
||||
// TODO: find out why
|
||||
in_auto_detect = false;
|
||||
auto_detect_succeed_consumed = false;
|
||||
time_start_auto_detect = 0;
|
||||
}
|
||||
|
||||
/* SetMenuColorView ************************************/
|
||||
|
||||
void SetMenuColorView::paint_sample() {
|
||||
@@ -1022,6 +1106,7 @@ void SettingsMenuView::on_populate() {
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<TouchCalibrationView>(); }},
|
||||
{"TouchThreshold", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<SetTouchscreenThresholdView>(); }},
|
||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push<SetConfigModeView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -362,12 +363,8 @@ class SetUIView : public View {
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_text};
|
||||
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{23 * 8, 14 * 16 + 2, 16, 16},
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
@@ -709,8 +706,6 @@ class SetConfigModeView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::fake_brightness_level_options;
|
||||
|
||||
class SetDisplayView : public View {
|
||||
public:
|
||||
SetDisplayView(NavigationView& nav);
|
||||
@@ -720,27 +715,8 @@ class SetDisplayView : public View {
|
||||
std::string title() const override { return "Display"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Limits screen brightness", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
OptionsField field_fake_brightness{
|
||||
{20 * 8, 8 * 16},
|
||||
6,
|
||||
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
|
||||
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
|
||||
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
|
||||
|
||||
Checkbox checkbox_brightness_switch{
|
||||
{1 * 8, 5 * 16},
|
||||
16,
|
||||
"Enable brightness adjust"};
|
||||
|
||||
Checkbox checkbox_invert_switch{
|
||||
{1 * 8, 10 * 16},
|
||||
{1 * 8, 2 * 16},
|
||||
23,
|
||||
"Invert colors (For IPS)"};
|
||||
|
||||
@@ -754,6 +730,77 @@ class SetDisplayView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::touchscreen_threshold;
|
||||
|
||||
class SetTouchscreenThresholdView : public View {
|
||||
public:
|
||||
SetTouchscreenThresholdView(NavigationView& nav);
|
||||
~SetTouchscreenThresholdView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Touch S"; };
|
||||
|
||||
private:
|
||||
bool in_auto_detect = false;
|
||||
uint16_t org_threshold = 0;
|
||||
uint8_t auto_detect_succeed_consumed = false; // prevent screen flash but can still change text content
|
||||
uint32_t time_start_auto_detect = 0;
|
||||
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Set touchscreen sensitivity", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "Or press auto detect button", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "FOLLOW INSTRUCTIONS", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 4 * 16}, "REBOOT TO APPLY", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 11 * 16}, "Threshold:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Text text_hint{
|
||||
{1 * 8, 7 * 16, screen_width - 2 * 8, 1 * 16},
|
||||
"DON'T TOUCH SCREEN"};
|
||||
|
||||
Text text_wait_timer{
|
||||
{1 * 8, 8 * 16, screen_width - 2 * 8, 1 * 16},
|
||||
"ETA 00:00"};
|
||||
|
||||
void on_frame_sync();
|
||||
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
*/
|
||||
NumberField field_threshold{
|
||||
{1 * 8 + 11 * 8 + 8, 11 * 16},
|
||||
4,
|
||||
{1, 1023},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_autodetect{
|
||||
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||
"Auto Detect"};
|
||||
Button button_reset{
|
||||
{16 * 8, 13 * 16, 12 * 8, 32},
|
||||
"Reset",
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
class SetMenuColorView : public View {
|
||||
public:
|
||||
SetMenuColorView(NavigationView& nav);
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
#include "ui_standalone_view.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
|
||||
#include "ui_font_fixed_5x8.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
|
||||
@@ -34,6 +40,16 @@ void fill_rectangle(int x, int y, int width, int height, uint16_t color) {
|
||||
painter.fill_rectangle({x, y, width, height}, ui::Color(color));
|
||||
}
|
||||
|
||||
void fill_rectangle_unrolled8(int x, int y, int width, int height, uint16_t color) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle_unrolled8({x, y, width, height}, ui::Color(color));
|
||||
}
|
||||
|
||||
void draw_bitmap(int x, int y, int width, int height, const uint8_t* pixels, uint16_t foreground, uint16_t background) {
|
||||
ui::Painter painter;
|
||||
painter.draw_bitmap({x, y}, {{width, height}, pixels}, ui::Color(foreground), ui::Color(background));
|
||||
}
|
||||
|
||||
void* alloc(size_t size) {
|
||||
void* p = chHeapAlloc(0x0, size);
|
||||
if (p == nullptr)
|
||||
@@ -45,6 +61,45 @@ uint64_t get_switches_state_ulong() {
|
||||
return get_switches_state().to_ulong();
|
||||
}
|
||||
|
||||
ui::Coord scroll_area_y(const ui::Coord y) {
|
||||
return portapack::display.scroll_area_y(y);
|
||||
}
|
||||
|
||||
void scroll_set_area(const ui::Coord top_y, const ui::Coord bottom_y) {
|
||||
portapack::display.scroll_set_area(top_y, bottom_y);
|
||||
}
|
||||
|
||||
void scroll_disable() {
|
||||
portapack::display.scroll_disable();
|
||||
}
|
||||
|
||||
ui::Coord scroll_set_position(const ui::Coord position) {
|
||||
return portapack::display.scroll_set_position(position);
|
||||
}
|
||||
|
||||
ui::Coord scroll(const int32_t delta) {
|
||||
return portapack::display.scroll(delta);
|
||||
}
|
||||
|
||||
bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
if (!dev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data_len == 0 || data == nullptr)
|
||||
return dev->i2c_write(nullptr, 0, cmd, cmd_len);
|
||||
|
||||
return dev->i2c_read(cmd, cmd_len, data, data_len);
|
||||
}
|
||||
|
||||
StandaloneView* standaloneView = nullptr;
|
||||
|
||||
void set_dirty() {
|
||||
if (standaloneView != nullptr)
|
||||
standaloneView->set_dirty();
|
||||
}
|
||||
|
||||
standalone_application_api_t api = {
|
||||
/* .malloc = */ &alloc,
|
||||
/* .calloc = */ &calloc,
|
||||
@@ -54,20 +109,78 @@ standalone_application_api_t api = {
|
||||
/* .fill_rectangle = */ &fill_rectangle,
|
||||
/* .swizzled_switches = */ &swizzled_switches,
|
||||
/* .get_switches_state = */ &get_switches_state_ulong,
|
||||
/* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
|
||||
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
|
||||
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
|
||||
/* .draw_bitmap = */ &draw_bitmap,
|
||||
/* .scroll_area_y = */ &scroll_area_y,
|
||||
/* .scroll_set_area = */ &scroll_set_area,
|
||||
/* .scroll_disable = */ &scroll_disable,
|
||||
/* .scroll_set_position = */ &scroll_set_position,
|
||||
/* .scroll = */ &scroll,
|
||||
/* .i2c_read = */ &i2c_read,
|
||||
/* .panic = */ &chDbgPanic,
|
||||
/* .set_dirty = */ &set_dirty,
|
||||
};
|
||||
|
||||
StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image)
|
||||
: nav_(nav), _app_image(std::move(app_image)) {
|
||||
get_application_information()->initialize(api);
|
||||
add_children({&dummy});
|
||||
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
|
||||
: nav_(nav),
|
||||
_app_image(*app_image) {
|
||||
if (app_image == nullptr) {
|
||||
chDbgPanic("Invalid application image");
|
||||
}
|
||||
|
||||
set_focusable(true);
|
||||
|
||||
standaloneView = this;
|
||||
}
|
||||
|
||||
void StandaloneView::focus() {
|
||||
dummy.focus();
|
||||
View::focus();
|
||||
}
|
||||
|
||||
void StandaloneView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (initialized &&
|
||||
get_application_information()->header_version > 1) {
|
||||
get_application_information()->PaintViewMirror();
|
||||
}
|
||||
}
|
||||
|
||||
void StandaloneView::on_focus() {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
get_application_information()->OnFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool StandaloneView::on_key(const KeyEvent key) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
return get_application_information()->OnKeyEvent((uint8_t)key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_encoder(const EncoderEvent event) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
return get_application_information()->OnEncoder((int32_t)event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_touch(const TouchEvent event) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
return get_application_information()->OnKeyboard((uint8_t)event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void StandaloneView::frame_sync() {
|
||||
@@ -79,4 +192,14 @@ void StandaloneView::frame_sync() {
|
||||
}
|
||||
}
|
||||
|
||||
void StandaloneView::on_after_attach() {
|
||||
context().focus_manager().setMirror(this);
|
||||
get_application_information()->initialize(api);
|
||||
}
|
||||
|
||||
void StandaloneView::on_before_detach() {
|
||||
get_application_information()->shutdown();
|
||||
context().focus_manager().clearMirror();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -31,28 +31,34 @@ namespace ui {
|
||||
|
||||
class StandaloneView : public View {
|
||||
public:
|
||||
StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image);
|
||||
virtual ~StandaloneView() override { get_application_information()->shutdown(); }
|
||||
|
||||
void focus() override;
|
||||
StandaloneView(NavigationView& nav, uint8_t* app_image);
|
||||
virtual ~StandaloneView() override {
|
||||
}
|
||||
|
||||
std::string title() const override { return (const char*)get_application_information()->app_name; };
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
void on_focus() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void on_after_attach() override;
|
||||
void on_before_detach() override;
|
||||
|
||||
void frame_sync();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<uint8_t[]> _app_image;
|
||||
uint8_t& _app_image;
|
||||
standalone_application_information_t* get_application_information() const {
|
||||
return reinterpret_cast<standalone_application_information_t*>(_app_image.get());
|
||||
return reinterpret_cast<standalone_application_information_t*>(&_app_image);
|
||||
}
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_sample{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_tone_search.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ToneSearchView::focus() {
|
||||
// field_frequency_min.focus();
|
||||
}
|
||||
|
||||
ToneSearchView::~ToneSearchView() {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
ToneSearchView::ToneSearchView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_rf_amp,
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "receiver_model.hpp"
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ToneSearchView : public View {
|
||||
public:
|
||||
ToneSearchView(NavigationView& nav);
|
||||
~ToneSearchView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Tone search"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{4 * 8, 0 * 16}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{11 * 8, 0 * 16}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{18 * 8, 0 * 16}};
|
||||
|
||||
/*
|
||||
MessageHandlerRegistration message_handler_frame_sync {
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if( this->fifo ) {
|
||||
ChannelSpectrum channel_spectrum;
|
||||
while( fifo->out(channel_spectrum) ) {
|
||||
this->on_channel_spectrum(channel_spectrum);
|
||||
}
|
||||
}
|
||||
this->do_timers();
|
||||
}
|
||||
};*/
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -213,6 +213,11 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
return "EmosE601x";
|
||||
case FPW_SolightTE44:
|
||||
return "SolightTE44";
|
||||
case FPW_Bresser3CH:
|
||||
case FPW_Bresser3CH_V1:
|
||||
return "Bresser3CH";
|
||||
case FPW_Vauno_EN8822:
|
||||
return "Vauno EN8822";
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
return "Unknown";
|
||||
@@ -532,6 +537,42 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
break;
|
||||
case FPW_Bresser3CH:
|
||||
ret.id = (data->decode_data >> 28) & 0xff;
|
||||
ret.channel = ((data->decode_data >> 27) & 0x01) | (((data->decode_data >> 26) & 0x01) << 1);
|
||||
// ret.btn = ((data->decode_data >> 25) & 0x1);
|
||||
ret.battery_low = ((data->decode_data >> 24) & 0x1);
|
||||
i16 = (data->decode_data >> 12) & 0x0fff;
|
||||
/* Handle signed data */
|
||||
if (i16 & 0x0800) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
ret.humidity = data->decode_data & 0xff;
|
||||
break;
|
||||
case FPW_Bresser3CH_V1:
|
||||
ret.id = (data->decode_data >> 32) & 0xff;
|
||||
ret.battery_low = ((data->decode_data >> 31) & 0x1);
|
||||
// ret.btn = (data->decode_data >> 30) & 0x1;
|
||||
ret.channel = (data->decode_data >> 28) & 0x3;
|
||||
ret.temp = (data->decode_data >> 16) & 0xfff;
|
||||
ret.temp = FProtoGeneral::locale_fahrenheit_to_celsius((float)(ret.temp - 900) / 10.0);
|
||||
ret.humidity = (data->decode_data >> 8) & 0xff;
|
||||
break;
|
||||
|
||||
case FPW_Vauno_EN8822:
|
||||
ret.id = (data->decode_data >> 34) & 0xff;
|
||||
ret.battery_low = (data->decode_data >> 33) & 0x01;
|
||||
ret.channel = ((data->decode_data >> 30) & 0x03);
|
||||
i16 = (data->decode_data >> 18) & 0x0fff;
|
||||
/* Handle signed data */
|
||||
if (i16 & 0x0800) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
ret.humidity = (data->decode_data >> 11) & 0x7f;
|
||||
|
||||
break;
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "emu_cc1101.hpp"
|
||||
|
||||
namespace cc1101 {
|
||||
|
||||
void CC1101Emu::whitening_init() {
|
||||
whitening_pn = 0x1FF;
|
||||
}
|
||||
|
||||
// See TI app note DN509
|
||||
uint8_t CC1101Emu::whiten_byte(uint8_t byte) {
|
||||
uint_fast8_t new_bit;
|
||||
|
||||
byte ^= (whitening_pn & 0xFF);
|
||||
|
||||
for (size_t step = 0; step < 8; step++) {
|
||||
new_bit = (whitening_pn & 1) ^ ((whitening_pn >> 5) & 1);
|
||||
whitening_pn >>= 1;
|
||||
whitening_pn |= (new_bit << 8);
|
||||
}
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
} /* namespace cc1101 */
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||
// If you can read this, you're a nerd. :P
|
||||
// Come join us at https://discord.gg/thepiratesreborn
|
||||
|
||||
#include "cvs_spam.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "oversample.hpp"
|
||||
#include "io_convert.hpp"
|
||||
#include "lfsr_random.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::cvs_spam {
|
||||
|
||||
void CVSSpamView::file_error(const std::filesystem::path& path, const std::string& error_details) {
|
||||
nav_.display_modal("Error",
|
||||
"File error:\n" + path.string() + "\n" + error_details);
|
||||
}
|
||||
|
||||
void CVSSpamView::refresh_list() {
|
||||
file_list.clear();
|
||||
current_page = page;
|
||||
uint32_t count = 0;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(cvsfiles_dir, u"*")) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
auto filename = entry.path().filename().string();
|
||||
auto extension = entry.path().extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
if (extension == ".c16" || extension == ".cu8") {
|
||||
if (count >= (page - 1) * 50 && count < page * 50) {
|
||||
file_list.push_back(entry.path());
|
||||
if (file_list.size() == 50) {
|
||||
page++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file_list.empty()) {
|
||||
if (page == 1) {
|
||||
menu_view.hidden(true);
|
||||
text_empty.hidden(false);
|
||||
} else {
|
||||
page = 1;
|
||||
refresh_list();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
menu_view.hidden(false);
|
||||
text_empty.hidden(true);
|
||||
menu_view.clear();
|
||||
for (const auto& file : file_list) {
|
||||
menu_view.add_item({file.filename().string(),
|
||||
ui::Theme::getInstance()->fg_green->foreground,
|
||||
nullptr,
|
||||
[this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
start_tx(menu_view.highlighted_index());
|
||||
}
|
||||
}});
|
||||
}
|
||||
page_info.set("Page " + std::to_string(current_page));
|
||||
menu_view.set_highlighted(0);
|
||||
}
|
||||
|
||||
if (file_list.size() < 50) {
|
||||
page = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void CVSSpamView::start_tx(const uint32_t id) {
|
||||
if (is_active()) {
|
||||
stop_tx();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t sample_rate = 250000;
|
||||
|
||||
current_file = cvsfiles_dir / file_list[id].filename();
|
||||
|
||||
File capture_file;
|
||||
auto open_error = capture_file.open(current_file);
|
||||
if (open_error) {
|
||||
file_error(current_file,
|
||||
"Cannot open file.\n"
|
||||
"Initial file check failed.\n"
|
||||
"Path: " +
|
||||
cvsfiles_dir.string() +
|
||||
"\n"
|
||||
"Error: " +
|
||||
std::to_string(static_cast<uint32_t>(open_error)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto metadata_path = get_metadata_path(current_file);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
if (!metadata) {
|
||||
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
|
||||
}
|
||||
|
||||
auto file_size = capture_file.size();
|
||||
capture_file.close();
|
||||
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
|
||||
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
|
||||
|
||||
auto reader = std::make_unique<FileConvertReader>();
|
||||
if (auto error = reader->open(current_file)) {
|
||||
file_error(current_file,
|
||||
"Cannot read C16 data.\n"
|
||||
"Check file format/perms.\n"
|
||||
"Rate: " +
|
||||
to_string_dec_uint(metadata->sample_rate) +
|
||||
"\n"
|
||||
"Size: " +
|
||||
to_string_dec_uint(file_size) +
|
||||
"\n"
|
||||
"Error: " +
|
||||
std::to_string(static_cast<uint32_t>(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
progressbar.set_value(0);
|
||||
|
||||
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
|
||||
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||
transmitter_model.enable();
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size,
|
||||
buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
void CVSSpamView::start_random_tx() {
|
||||
if (is_active()) {
|
||||
stop_tx();
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_list.empty()) {
|
||||
nav_.display_modal("Error", "No files found!");
|
||||
return;
|
||||
}
|
||||
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
size_t random_index = lfsr_v % file_list.size();
|
||||
|
||||
const uint32_t sample_rate = 250000;
|
||||
current_file = cvsfiles_dir / file_list[random_index].filename();
|
||||
|
||||
File capture_file;
|
||||
auto open_error = capture_file.open(current_file);
|
||||
if (open_error) {
|
||||
file_error(current_file, "Cannot open file.\nInitial check failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto metadata_path = get_metadata_path(current_file);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
if (!metadata) {
|
||||
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
|
||||
}
|
||||
|
||||
capture_file.close();
|
||||
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
|
||||
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
|
||||
|
||||
auto reader = std::make_unique<FileConvertReader>();
|
||||
if (auto error = reader->open(current_file)) {
|
||||
file_error(current_file, "Cannot read file data.");
|
||||
return;
|
||||
}
|
||||
|
||||
progressbar.set_value(0);
|
||||
|
||||
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
|
||||
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||
transmitter_model.enable();
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size,
|
||||
buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
void CVSSpamView::on_tx_progress(const uint32_t progress) {
|
||||
if (is_active()) {
|
||||
progressbar.set_value(progress % 100);
|
||||
progressbar.set_style(Theme::getInstance()->fg_red);
|
||||
}
|
||||
}
|
||||
|
||||
void CVSSpamView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
bool CVSSpamView::is_active() const {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void CVSSpamView::stop_tx() {
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
thread_sync_complete = false;
|
||||
chaos_mode = false;
|
||||
progressbar.set_value(0);
|
||||
chThdSleepMilliseconds(50);
|
||||
}
|
||||
|
||||
CVSSpamView::CVSSpamView(NavigationView& nav)
|
||||
: nav_{nav},
|
||||
current_file{} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
add_children({&menu_view,
|
||||
&text_empty,
|
||||
&button_prev_page,
|
||||
&button_next_page,
|
||||
&page_info,
|
||||
&button_send,
|
||||
&button_chaos,
|
||||
&button_stop,
|
||||
&progressbar});
|
||||
|
||||
button_send.set_style(Theme::getInstance()->fg_magenta);
|
||||
button_chaos.set_style(Theme::getInstance()->fg_yellow);
|
||||
button_stop.set_style(Theme::getInstance()->fg_red);
|
||||
|
||||
transmitter_model.set_sampling_rate(1536000);
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_tx_gain(47);
|
||||
transmitter_model.set_channel_bandwidth(1750000);
|
||||
|
||||
refresh_list();
|
||||
|
||||
button_prev_page.on_select = [this](Button&) {
|
||||
if (is_active()) return;
|
||||
if (current_page == 1) return;
|
||||
if (current_page == 2) page = 1;
|
||||
page = current_page - 1;
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
button_next_page.on_select = [this](Button&) {
|
||||
if (is_active()) return;
|
||||
refresh_list();
|
||||
};
|
||||
|
||||
button_send.on_select = [this](Button&) {
|
||||
if (!file_list.empty()) {
|
||||
start_tx(menu_view.highlighted_index());
|
||||
}
|
||||
};
|
||||
|
||||
button_chaos.on_select = [this](Button&) {
|
||||
if (is_active()) {
|
||||
chaos_mode = false;
|
||||
stop_tx();
|
||||
} else {
|
||||
chaos_mode = true;
|
||||
start_random_tx();
|
||||
}
|
||||
};
|
||||
|
||||
button_stop.on_select = [this](Button&) {
|
||||
if (is_active()) {
|
||||
stop_tx();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
CVSSpamView::~CVSSpamView() {
|
||||
stop_tx();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::cvs_spam
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||
// If you can read this, you're a nerd. :P
|
||||
// Come join us at https://discord.gg/thepiratesreborn
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::cvs_spam {
|
||||
|
||||
class CVSSpamView : public View {
|
||||
public:
|
||||
explicit CVSSpamView(NavigationView& nav);
|
||||
~CVSSpamView();
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "CVS Spam"; };
|
||||
|
||||
private:
|
||||
static constexpr size_t read_size = 0x4000;
|
||||
static constexpr size_t buffer_count = 3;
|
||||
|
||||
lfsr_word_t lfsr_v = 1;
|
||||
bool chaos_mode{false};
|
||||
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
bool thread_sync_complete{false};
|
||||
std::filesystem::path current_file;
|
||||
|
||||
bool is_active() const;
|
||||
void stop_tx();
|
||||
void file_error(const std::filesystem::path& path, const std::string& error_details);
|
||||
void refresh_list();
|
||||
void start_tx(const uint32_t id);
|
||||
void start_random_tx();
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
|
||||
uint32_t page = 1;
|
||||
uint32_t current_page = 1;
|
||||
std::vector<std::filesystem::path> file_list{};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 180},
|
||||
true};
|
||||
|
||||
Text text_empty{
|
||||
{7 * 8, 12 * 8, 16 * 8, 16},
|
||||
"Empty directory!"};
|
||||
|
||||
Button button_prev_page{
|
||||
{0, 180, 50, 32},
|
||||
"<"};
|
||||
|
||||
Button button_next_page{
|
||||
{190, 180, 50, 32},
|
||||
">"};
|
||||
|
||||
Text page_info{
|
||||
{95, 188, 50, 16}};
|
||||
|
||||
Button button_send{
|
||||
{0, 212, 75, 36},
|
||||
"CALL"};
|
||||
|
||||
Button button_chaos{
|
||||
{82, 212, 75, 36},
|
||||
"CHAOS"};
|
||||
|
||||
Button button_stop{
|
||||
{165, 212, 75, 36},
|
||||
LanguageHelper::currentMessages[LANG_STOP]};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0, 256, 240, 44}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
ready_signal = true;
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_done{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
if (message.return_code == ReplayThread::END_OF_FILE) {
|
||||
if (chaos_mode) {
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
size_t random_index = lfsr_v % file_list.size();
|
||||
menu_view.set_highlighted(random_index);
|
||||
chThdSleepMilliseconds(100);
|
||||
start_tx(random_index);
|
||||
} else {
|
||||
thread_sync_complete = true;
|
||||
stop_tx();
|
||||
}
|
||||
} else if (message.return_code == ReplayThread::READ_ERROR) {
|
||||
file_error(file_list[menu_view.highlighted_index()], "Read error during playback");
|
||||
if (chaos_mode) {
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
size_t random_index = lfsr_v % file_list.size();
|
||||
menu_view.set_highlighted(random_index);
|
||||
chThdSleepMilliseconds(100);
|
||||
start_tx(random_index);
|
||||
}
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::cvs_spam
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
|
||||
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
|
||||
// If you can read this, you're a nerd. :P
|
||||
// Come join us at https://discord.gg/thepiratesreborn
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "cvs_spam.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::cvs_spam {
|
||||
void initialize_app(NavigationView& nav) {
|
||||
nav.push<CVSSpamView>();
|
||||
}
|
||||
} // namespace ui::external_app::cvs_spam
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::cvs_spam::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"CVS Spam",
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
|
||||
ui::Color::red().v,
|
||||
app_location_t::TX,
|
||||
{'P', 'R', 'E', 'P'},
|
||||
0x00000000};
|
||||
}
|
||||
+15
-1
@@ -106,7 +106,18 @@ set(EXTCPPSRC
|
||||
#acars
|
||||
external/acars_rx/main.cpp
|
||||
external/acars_rx/acars_app.cpp
|
||||
|
||||
|
||||
#shoppingcart_lock
|
||||
external/shoppingcart_lock/main.cpp
|
||||
external/shoppingcart_lock/shoppingcart_lock.cpp
|
||||
|
||||
#ookbrute
|
||||
external/ookbrute/main.cpp
|
||||
external/ookbrute/ui_ookbrute.cpp
|
||||
|
||||
#cvs_spam
|
||||
external/cvs_spam/main.cpp
|
||||
external/cvs_spam/cvs_spam.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -128,6 +139,7 @@ set(EXTAPPLIST
|
||||
foxhunt_rx
|
||||
audio_test
|
||||
wardrivemap
|
||||
cvs_spam
|
||||
tpmsrx
|
||||
protoview
|
||||
adsbtx
|
||||
@@ -135,4 +147,6 @@ set(EXTAPPLIST
|
||||
sstvtx
|
||||
random_password
|
||||
#acars_rx
|
||||
ookbrute
|
||||
shoppingcart_lock
|
||||
)
|
||||
|
||||
+18
-1
@@ -48,6 +48,9 @@ MEMORY
|
||||
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
|
||||
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
|
||||
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
|
||||
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
|
||||
ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k
|
||||
ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -166,7 +169,6 @@ SECTIONS
|
||||
*(*ui*external_app*tpmsrx*);
|
||||
} > ram_external_app_tpmsrx
|
||||
|
||||
|
||||
.external_app_protoview : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_protoview.application_information));
|
||||
@@ -204,7 +206,22 @@ SECTIONS
|
||||
*(*ui*external_app*acars_rx*);
|
||||
} > ram_external_app_acars_rx
|
||||
|
||||
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
|
||||
*(*ui*external_app*shoppingcart_lock*);
|
||||
} > ram_external_app_shoppingcart_lock
|
||||
|
||||
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_cvs_spam.application_information));
|
||||
*(*ui*external_app*cvs_spam*);
|
||||
} > ram_external_app_cvs_spam
|
||||
|
||||
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_ookbrute.application_information));
|
||||
*(*ui*external_app*ookbrute*);
|
||||
} > ram_external_app_ookbrute
|
||||
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_ookbrute.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::ookbrute {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<OokBruteView>();
|
||||
}
|
||||
} // namespace ui::external_app::ookbrute
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_ookbrute.application_information"), used)) application_information_t _application_information_ookbrute = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::ookbrute::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "OokBrute",
|
||||
/*.bitmap_data = */ {
|
||||
0x20,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0xE0,
|
||||
0x07,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x30,
|
||||
0x0C,
|
||||
0x30,
|
||||
0x0C,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x70,
|
||||
0x0D,
|
||||
0xB0,
|
||||
0x0E,
|
||||
0x70,
|
||||
0x0D,
|
||||
0xB0,
|
||||
0x0E,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xE0,
|
||||
0x07,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_ookbrute.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::ookbrute {
|
||||
|
||||
void OokBruteView::focus() {
|
||||
button_startstop.focus();
|
||||
}
|
||||
|
||||
OokBruteView::OokBruteView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({
|
||||
&button_startstop,
|
||||
&field_frequency,
|
||||
&tx_view,
|
||||
&options_atkmode,
|
||||
&field_start,
|
||||
&field_stop,
|
||||
});
|
||||
|
||||
button_startstop.on_select = [this](Button&) {
|
||||
if (is_running) {
|
||||
is_running = false;
|
||||
stop();
|
||||
} else {
|
||||
is_running = true;
|
||||
start();
|
||||
}
|
||||
};
|
||||
|
||||
options_atkmode.on_change = [this](size_t, int32_t i) {
|
||||
update_start_stop(i);
|
||||
validate_start_stop();
|
||||
};
|
||||
field_start.on_change = [this](int32_t) {
|
||||
validate_start_stop();
|
||||
};
|
||||
field_stop.on_change = [this](int32_t) {
|
||||
validate_start_stop();
|
||||
};
|
||||
update_start_stop(0);
|
||||
}
|
||||
|
||||
void OokBruteView::update_start_stop(uint32_t proto) {
|
||||
uint8_t bits = 12;
|
||||
switch (proto) {
|
||||
default:
|
||||
case 0:
|
||||
case 2:
|
||||
case 4:
|
||||
bits = 12;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
bits = 24;
|
||||
break;
|
||||
}
|
||||
uint32_t max = (1 << bits) - 1;
|
||||
field_start.set_range(0, max);
|
||||
field_stop.set_range(0, max);
|
||||
field_start.set_value(0);
|
||||
field_stop.set_value(max);
|
||||
}
|
||||
|
||||
void OokBruteView::validate_start_stop() {
|
||||
if (field_start.value() > field_stop.value()) {
|
||||
field_start.set_value(field_stop.value());
|
||||
}
|
||||
if (field_stop.value() < field_start.value()) {
|
||||
field_stop.set_value(field_start.value());
|
||||
}
|
||||
}
|
||||
|
||||
void OokBruteView::generate_packet() {
|
||||
uint32_t protocol = options_atkmode.selected_index_value();
|
||||
uint8_t byte = 0;
|
||||
size_t bitstream_length = 0;
|
||||
uint8_t* bitstream = shared_memory.bb_data.data; // max 512 in size
|
||||
uint32_t samples_per_bit = 0; // OOK_SAMPLERATE * bit_duration_in_sec
|
||||
std::string dataFormat = "";
|
||||
std::string zero = "";
|
||||
std::string one = "";
|
||||
uint16_t databits = 0;
|
||||
uint16_t repeat = 1;
|
||||
uint16_t pause_sym = 0;
|
||||
if (protocol == 0) { // came 12
|
||||
samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
|
||||
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
|
||||
databits = 12;
|
||||
zero = "011";
|
||||
one = "001";
|
||||
repeat = 2;
|
||||
pause_sym = 0;
|
||||
}
|
||||
|
||||
if (protocol == 1) { // came24
|
||||
samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
|
||||
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
|
||||
databits = 24;
|
||||
zero = "011";
|
||||
one = "001";
|
||||
repeat = 2;
|
||||
pause_sym = 0;
|
||||
}
|
||||
if (protocol == 2) { // nice12
|
||||
samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
|
||||
dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
|
||||
databits = 12;
|
||||
zero = "011";
|
||||
one = "001";
|
||||
repeat = 2;
|
||||
pause_sym = 0;
|
||||
}
|
||||
if (protocol == 3) { // nice24
|
||||
samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
|
||||
dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
|
||||
databits = 24;
|
||||
zero = "011";
|
||||
one = "001";
|
||||
repeat = 2;
|
||||
pause_sym = 0;
|
||||
}
|
||||
if (protocol == 4) { // holtek_ht12
|
||||
samples_per_bit = OOK_SAMPLERATE * (390.0 / 1000000.0);
|
||||
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC00000000000"; // 36 0 preamble +start bit + data.
|
||||
databits = 12;
|
||||
zero = "011";
|
||||
one = "001";
|
||||
repeat = 2;
|
||||
pause_sym = 0;
|
||||
}
|
||||
if (protocol == 5) { // princeton24
|
||||
samples_per_bit = OOK_SAMPLERATE * (450.0 / 1000000.0); // long = 3*
|
||||
dataFormat = "000000000000000000000000000000000000CCCCCCCCCCCCCCCCCCCCCCCC10000000"; //
|
||||
databits = 24;
|
||||
zero = "1000";
|
||||
one = "1110";
|
||||
repeat = 6;
|
||||
pause_sym = 0;
|
||||
}
|
||||
|
||||
std::string fragments = ""; // storage
|
||||
|
||||
uint16_t cdb = 0; // current data bit
|
||||
for (auto c : dataFormat) { // generate fragments from template
|
||||
if (c == '0') fragments += '0';
|
||||
if (c == '1') fragments += '1';
|
||||
if (c == 'C') {
|
||||
if (counter & (1 << (databits - cdb - 1))) {
|
||||
fragments += one;
|
||||
} else {
|
||||
fragments += zero;
|
||||
}
|
||||
cdb++;
|
||||
}
|
||||
}
|
||||
|
||||
// create bitstream
|
||||
for (auto c : fragments) {
|
||||
byte <<= 1;
|
||||
if (c != '0') byte |= 1;
|
||||
if ((bitstream_length & 7) == 7)
|
||||
bitstream[bitstream_length >> 3] = byte;
|
||||
bitstream_length++;
|
||||
}
|
||||
|
||||
// Finish last byte if needed
|
||||
size_t padding = 8 - (bitstream_length & 7);
|
||||
if (padding != 8) {
|
||||
byte <<= padding;
|
||||
bitstream[(bitstream_length + padding - 1) >> 3] = byte;
|
||||
padding++;
|
||||
}
|
||||
|
||||
// send bitstream
|
||||
baseband::set_ook_data(
|
||||
bitstream_length,
|
||||
samples_per_bit,
|
||||
repeat,
|
||||
pause_sym,
|
||||
0);
|
||||
}
|
||||
|
||||
void OokBruteView::stop() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
|
||||
}
|
||||
|
||||
void OokBruteView::start() {
|
||||
counter = field_start.value();
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
transmitter_model.enable();
|
||||
button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
|
||||
generate_packet();
|
||||
}
|
||||
|
||||
void OokBruteView::on_tx_progress(const bool done) {
|
||||
if (done) {
|
||||
if (is_running) {
|
||||
counter++;
|
||||
field_start.set_value(counter);
|
||||
if (counter > (uint32_t)field_stop.value()) {
|
||||
stop();
|
||||
} else {
|
||||
generate_packet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OokBruteView::~OokBruteView() {
|
||||
is_running = false;
|
||||
stop();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::ookbrute
|
||||
|
||||
/*
|
||||
|
||||
https://web.archive.org/web/20230331125843/https://phreakerclub.com/447
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UI_OOKBRUTE_H__
|
||||
#define __UI_OOKBRUTE_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::ookbrute {
|
||||
|
||||
#define OOK_SAMPLERATE 2280000U
|
||||
|
||||
class OokBruteView : public View {
|
||||
public:
|
||||
OokBruteView(NavigationView& nav);
|
||||
~OokBruteView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override {
|
||||
return "OokBrute";
|
||||
};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 0 * 16},
|
||||
/*short_ui*/ true};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_ookbrute", app_settings::Mode::TX};
|
||||
|
||||
Button button_startstop{
|
||||
{0, 3 * 16, 96, 24},
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
|
||||
NumberField field_start{
|
||||
{0 * 8, 1 * 16},
|
||||
8,
|
||||
{0, 2500},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_stop{
|
||||
{11 * 8, 1 * 16},
|
||||
9,
|
||||
{0, 2500},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
|
||||
OptionsField options_atkmode{
|
||||
{0 * 8, 2 * 16},
|
||||
12,
|
||||
{{"Came12", 0},
|
||||
{"Came24", 1},
|
||||
{"Nice12", 2},
|
||||
{"Nice24", 3},
|
||||
{"Holtek12", 4},
|
||||
{"Princeton24", 5}}};
|
||||
|
||||
bool is_running{false};
|
||||
|
||||
uint32_t counter = 0; // for packet change
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void on_tx_progress(const bool done);
|
||||
void validate_start_stop();
|
||||
void update_start_stop(uint32_t proto);
|
||||
void generate_packet();
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.done);
|
||||
}};
|
||||
};
|
||||
}; // namespace ui::external_app::ookbrute
|
||||
|
||||
#endif /*__UI_OOKBRUTE_H__*/
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
// RocketGod's Shopping Cart Lock app
|
||||
// https://betaskynet.com
|
||||
#include "ui.hpp"
|
||||
#include "shoppingcart_lock.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::shoppingcart_lock {
|
||||
void initialize_app(NavigationView& nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
nav.push<ShoppingCartLock>();
|
||||
}
|
||||
} // namespace ui::external_app::shoppingcart_lock
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_shoppingcart_lock.application_information"), used)) application_information_t _application_information_shoppingcart_lock = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::shoppingcart_lock::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"Cart Lock",
|
||||
{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7E,
|
||||
0x7E,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7E,
|
||||
0x81,
|
||||
0x81,
|
||||
0x7E,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7E,
|
||||
0x81,
|
||||
0x81,
|
||||
0x81,
|
||||
0x81,
|
||||
0x7E,
|
||||
0x00,
|
||||
},
|
||||
ui::Color::red().v,
|
||||
app_location_t::UTILITIES,
|
||||
{'P', 'A', 'T', 'X'},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// RocketGod's Shopping Cart Lock app
|
||||
// https://betaskynet.com
|
||||
#include "shoppingcart_lock.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::shoppingcart_lock {
|
||||
|
||||
void ShoppingCartLock::log_event(const std::string& message) {
|
||||
static const size_t MAX_LOG_LINES = 50;
|
||||
static std::vector<std::string> message_history;
|
||||
|
||||
message_history.push_back(message);
|
||||
if (message_history.size() > MAX_LOG_LINES) {
|
||||
message_history.erase(message_history.begin());
|
||||
menu_view.clear();
|
||||
for (const auto& msg : message_history) {
|
||||
menu_view.add_item({msg,
|
||||
ui::Theme::getInstance()->fg_green->foreground,
|
||||
nullptr,
|
||||
[](KeyEvent) {}});
|
||||
}
|
||||
} else {
|
||||
menu_view.add_item({message,
|
||||
ui::Theme::getInstance()->fg_green->foreground,
|
||||
nullptr,
|
||||
[](KeyEvent) {}});
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(menu_view.item_count() - 1);
|
||||
}
|
||||
|
||||
bool ShoppingCartLock::is_active() const {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void ShoppingCartLock::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
void ShoppingCartLock::stop() {
|
||||
log_event(">>> STOP_SEQUENCE_START");
|
||||
if (is_active()) {
|
||||
log_event("... Resetting Replay Thread");
|
||||
replay_thread.reset();
|
||||
}
|
||||
log_event("... Stopping Audio Output");
|
||||
audio::output::stop();
|
||||
|
||||
log_event("... Resetting State Variables");
|
||||
transmitter_model.disable();
|
||||
ready_signal = false;
|
||||
thread_sync_complete = false;
|
||||
looping = false;
|
||||
current_file = "";
|
||||
log_event("<<< STOP_SEQUENCE_COMPLETE");
|
||||
}
|
||||
|
||||
std::string ShoppingCartLock::list_wav_files() {
|
||||
log_event(">>> WAV_SCAN_START");
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
bool found_lock = false;
|
||||
bool found_unlock = false;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(wav_dir, u"*")) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
auto filename = entry.path().filename().string();
|
||||
std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);
|
||||
|
||||
if (filename == shoppingcart_lock_file || filename == shoppingcart_unlock_file) {
|
||||
std::string file_path = (wav_dir / filename).string();
|
||||
if (reader->open(file_path)) {
|
||||
if (filename == shoppingcart_lock_file) {
|
||||
found_lock = true;
|
||||
log_event("... Found: " + shoppingcart_lock_file);
|
||||
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
|
||||
log_event("Channels: " + std::to_string(reader->channels()));
|
||||
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
|
||||
}
|
||||
if (filename == shoppingcart_unlock_file) {
|
||||
found_unlock = true;
|
||||
log_event("... Found: " + shoppingcart_unlock_file);
|
||||
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
|
||||
log_event("Channels: " + std::to_string(reader->channels()));
|
||||
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found_lock && found_unlock) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_lock || !found_unlock) {
|
||||
log_event("!!! Missing Required Files:");
|
||||
if (!found_lock) log_event("!!! Missing: " + shoppingcart_lock_file);
|
||||
if (!found_unlock) log_event("!!! Missing: " + shoppingcart_unlock_file);
|
||||
menu_view.hidden(true);
|
||||
text_empty.hidden(false);
|
||||
} else {
|
||||
log_event("... All Required Files Found");
|
||||
menu_view.hidden(false);
|
||||
text_empty.hidden(true);
|
||||
}
|
||||
|
||||
log_event("<<< WAV_SCAN_COMPLETE");
|
||||
return found_lock && found_unlock ? "Required WAV files found" : "Missing required WAV files";
|
||||
}
|
||||
|
||||
void ShoppingCartLock::wait_for_thread() {
|
||||
uint32_t timeout = 100;
|
||||
while (!ready_signal && timeout > 0) {
|
||||
chThdYield();
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
void ShoppingCartLock::restart_playback() {
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
std::string file_path = (wav_dir / current_file).string();
|
||||
|
||||
if (!reader->open(file_path)) return;
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
BUFFER_SIZE,
|
||||
NUM_BUFFERS,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
log_event(">> SENDING <<");
|
||||
audio::output::start();
|
||||
transmitter_model.enable();
|
||||
}
|
||||
|
||||
void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
stop();
|
||||
|
||||
std::string file_path = (wav_dir / filename).string();
|
||||
if (!reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Cannot open " + filename);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t wav_sample_rate = reader->sample_rate();
|
||||
const uint16_t wav_bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
current_file = filename;
|
||||
looping = loop;
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
BUFFER_SIZE,
|
||||
NUM_BUFFERS,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
wait_for_thread();
|
||||
|
||||
log_event("... Configuring Baseband");
|
||||
|
||||
const uint32_t bb_sample_rate = 1536000;
|
||||
const uint32_t decimation = bb_sample_rate / wav_sample_rate;
|
||||
|
||||
baseband::set_audiotx_config(
|
||||
bb_sample_rate / decimation,
|
||||
0.0f,
|
||||
5.0f,
|
||||
wav_bits_per_sample,
|
||||
wav_bits_per_sample,
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
baseband::set_sample_rate(wav_sample_rate);
|
||||
|
||||
log_event("... Starting Audio Output");
|
||||
audio::output::start();
|
||||
log_event("... Setting Max Volume");
|
||||
audio::headphone::set_volume(audio::headphone::volume_range().max);
|
||||
|
||||
transmitter_model.enable();
|
||||
|
||||
log_event(">>> Playback Started <<<");
|
||||
}
|
||||
|
||||
ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&menu_view,
|
||||
&text_empty,
|
||||
&button_lock,
|
||||
&button_unlock,
|
||||
&button_stop});
|
||||
|
||||
button_lock.on_select = [this](Button&) {
|
||||
if (is_active()) stop();
|
||||
log_event(">>> LOCK_SEQUENCE_START");
|
||||
play_audio(shoppingcart_lock_file, true);
|
||||
};
|
||||
|
||||
button_unlock.on_select = [this](Button&) {
|
||||
if (is_active()) stop();
|
||||
log_event(">>> UNLOCK_SEQUENCE_START");
|
||||
play_audio(shoppingcart_unlock_file, true);
|
||||
};
|
||||
|
||||
button_stop.on_select = [this](Button&) {
|
||||
log_event(">>> STOPPING AUDIO");
|
||||
stop();
|
||||
};
|
||||
|
||||
list_wav_files();
|
||||
|
||||
log_event("[+] INITIALIZATION COMPLETE");
|
||||
log_event("[+] PORTAPACK ARMED");
|
||||
log_event("[*] STATUS: READY");
|
||||
}
|
||||
|
||||
ShoppingCartLock::~ShoppingCartLock() {
|
||||
stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::shoppingcart_lock
|
||||
@@ -0,0 +1,102 @@
|
||||
// RocketGod's Shopping Cart Lock app
|
||||
// https://betaskynet.com
|
||||
#pragma once
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
namespace ui::external_app::shoppingcart_lock {
|
||||
|
||||
class ShoppingCartLock : public View {
|
||||
public:
|
||||
explicit ShoppingCartLock(NavigationView& nav);
|
||||
~ShoppingCartLock();
|
||||
|
||||
ShoppingCartLock(const ShoppingCartLock&) = delete;
|
||||
ShoppingCartLock& operator=(const ShoppingCartLock&) = delete;
|
||||
|
||||
std::string title() const override { return "Cart Lock"; };
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
static constexpr size_t BUFFER_SIZE = 8192;
|
||||
static constexpr size_t NUM_BUFFERS = 8;
|
||||
const std::string shoppingcart_lock_file{"shopping_cart_lock.wav"};
|
||||
const std::string shoppingcart_unlock_file{"shopping_cart_unlock.wav"};
|
||||
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
bool thread_sync_complete{false};
|
||||
bool looping{false};
|
||||
std::string current_file{};
|
||||
|
||||
struct WAVProperties {
|
||||
uint32_t sample_rate;
|
||||
uint16_t bits_per_sample;
|
||||
size_t file_size;
|
||||
};
|
||||
|
||||
void log_event(const std::string& message);
|
||||
std::string list_wav_files();
|
||||
void handle_error(const std::string& message);
|
||||
void play_audio(const std::string& filename, bool loop = false);
|
||||
void stop();
|
||||
bool is_active() const;
|
||||
void wait_for_thread();
|
||||
void restart_playback();
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 150},
|
||||
true};
|
||||
|
||||
Text text_empty{
|
||||
{40, 70, 160, 16},
|
||||
"RocketGod was here"};
|
||||
|
||||
Button button_lock{
|
||||
{40, 165, 160, 35},
|
||||
LanguageHelper::currentMessages[LANG_LOCK]};
|
||||
|
||||
Button button_unlock{
|
||||
{40, 205, 160, 35},
|
||||
LanguageHelper::currentMessages[LANG_UNLOCK]};
|
||||
|
||||
Button button_stop{
|
||||
{40, 245, 160, 35},
|
||||
LanguageHelper::currentMessages[LANG_STOP]};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
ready_signal = true;
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_done{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
if (message.return_code == ReplayThread::END_OF_FILE && looping) {
|
||||
if (is_active()) {
|
||||
chThdSleepMilliseconds(50);
|
||||
restart_playback();
|
||||
}
|
||||
} else {
|
||||
thread_sync_complete = true;
|
||||
stop();
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::shoppingcart_lock
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __UI_SPECTRUM_PAINTER_H
|
||||
#define __UI_SPECTRUM_PAINTER_H
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
@@ -165,3 +166,5 @@ class SpectrumPainterView : public View {
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::spainter
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __UI_SPECTRUM_PAINTER_IMAGE_H
|
||||
#define __UI_SPECTRUM_PAINTER_IMAGE_H
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
@@ -64,3 +65,5 @@ class SpectrumInputImageView : public View {
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::spainter
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __UI_SPECTRUM_PAINTER_TEXT_H
|
||||
#define __UI_SPECTRUM_PAINTER_TEXT_H
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
@@ -110,3 +111,5 @@ class SpectrumInputTextView : public View {
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::spainter
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,7 @@ const std::filesystem::path audio_dir = u"AUDIO";
|
||||
const std::filesystem::path blerx_dir = u"BLERX";
|
||||
const std::filesystem::path bletx_dir = u"BLETX";
|
||||
const std::filesystem::path captures_dir = u"CAPTURES";
|
||||
const std::filesystem::path cvsfiles_dir = u"CVSFILES";
|
||||
const std::filesystem::path debug_dir = u"DEBUG";
|
||||
const std::filesystem::path firmware_dir = u"FIRMWARE";
|
||||
const std::filesystem::path freqman_dir = u"FREQMAN";
|
||||
|
||||
@@ -32,6 +32,7 @@ extern const std::filesystem::path audio_dir;
|
||||
extern const std::filesystem::path blerx_dir;
|
||||
extern const std::filesystem::path bletx_dir;
|
||||
extern const std::filesystem::path captures_dir;
|
||||
extern const std::filesystem::path cvsfiles_dir;
|
||||
extern const std::filesystem::path debug_dir;
|
||||
extern const std::filesystem::path firmware_dir;
|
||||
extern const std::filesystem::path freqman_dir;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "i2c_device_to_host.h"
|
||||
#include "i2cdevmanager_c_api.h"
|
||||
#include <string.h>
|
||||
|
||||
I2CShellDriver I2CD1;
|
||||
|
||||
// pp to i2c data tx
|
||||
static void onotifyi2c(GenericQueue* qp) {
|
||||
I2CShellDriver* sdp = chQGetLink(qp);
|
||||
uint8_t buff[I2CSHELL_BUFFERS_SIZE];
|
||||
int n = chOQGetFullI(&sdp->oqueue);
|
||||
if (n > I2CSHELL_BUFFERS_SIZE) n = I2CSHELL_BUFFERS_SIZE; // don't overflow
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
buff[i] = chOQGetI(&sdp->oqueue);
|
||||
}
|
||||
int ret;
|
||||
chSysUnlock();
|
||||
do {
|
||||
ret = oNofityI2cFromShell(&buff[0], n); // i2c_write
|
||||
if (ret == -1)
|
||||
chThdSleepMilliseconds(1);
|
||||
} while (ret == -1);
|
||||
chSysLock();
|
||||
}
|
||||
}
|
||||
|
||||
static size_t write(void* ip, const uint8_t* bp, size_t n) {
|
||||
return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp,
|
||||
n, TIME_INFINITE);
|
||||
}
|
||||
|
||||
static size_t read(void* ip, uint8_t* bp, size_t n) {
|
||||
return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp,
|
||||
n, TIME_INFINITE);
|
||||
}
|
||||
|
||||
static msg_t put(void* ip, uint8_t b) {
|
||||
return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, TIME_INFINITE);
|
||||
}
|
||||
|
||||
static msg_t get(void* ip) {
|
||||
return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, TIME_INFINITE);
|
||||
}
|
||||
|
||||
static msg_t putt(void* ip, uint8_t b, systime_t timeout) {
|
||||
return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, timeout);
|
||||
}
|
||||
|
||||
static msg_t gett(void* ip, systime_t timeout) {
|
||||
return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, timeout);
|
||||
}
|
||||
|
||||
static size_t writet(void* ip, const uint8_t* bp, size_t n, systime_t time) {
|
||||
return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp, n, time);
|
||||
}
|
||||
|
||||
static size_t readt(void* ip, uint8_t* bp, size_t n, systime_t time) {
|
||||
return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp, n, time);
|
||||
}
|
||||
|
||||
static const struct I2CShellDriverVMT vmt = {
|
||||
write, read, put, get,
|
||||
putt, gett, writet, readt};
|
||||
|
||||
void init_i2c_shell_driver(I2CShellDriver* sdp) {
|
||||
sdp->vmt = &vmt;
|
||||
chIQInit(&sdp->iqueue, sdp->ib, I2CSHELL_BUFFERS_SIZE, NULL, sdp);
|
||||
chOQInit(&sdp->oqueue, sdp->ob, I2CSHELL_BUFFERS_SIZE, onotifyi2c, sdp);
|
||||
}
|
||||
|
||||
// i2c->pp data rx
|
||||
void complete_i2chost_to_device_transfer(uint8_t* data, size_t length) {
|
||||
chSysLock();
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&I2CD1.iqueue, data[i]);
|
||||
if (ret == Q_FULL) {
|
||||
chSysUnlock();
|
||||
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
|
||||
chSysLock();
|
||||
}
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
chSysUnlock();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __I2C_DEVICE_TO_HOST_H
|
||||
#define __I2C_DEVICE_TO_HOST_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#ifndef I2CSHELL_BUFFERS_SIZE
|
||||
#define I2CSHELL_BUFFERS_SIZE 64
|
||||
#endif
|
||||
|
||||
struct I2CShellDriverVMT {
|
||||
_base_asynchronous_channel_methods
|
||||
};
|
||||
|
||||
struct I2CShellDriver {
|
||||
/** @brief Virtual Methods Table.*/
|
||||
const struct I2CShellDriverVMT* vmt;
|
||||
InputQueue iqueue; /* Output queue.*/
|
||||
OutputQueue oqueue; /* Input circular buffer.*/
|
||||
uint8_t ib[I2CSHELL_BUFFERS_SIZE]; /* Output circular buffer.*/
|
||||
uint8_t ob[I2CSHELL_BUFFERS_SIZE];
|
||||
};
|
||||
|
||||
typedef struct I2CShellDriver I2CShellDriver;
|
||||
|
||||
extern I2CShellDriver I2CD1;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void init_i2c_shell_driver(I2CShellDriver* sdp);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __IO_H
|
||||
#define __IO_H
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
@@ -38,3 +39,5 @@ class Writer {
|
||||
};
|
||||
|
||||
} /* namespace stream */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __IO_CONVERT_H
|
||||
#define __IO_CONVERT_H
|
||||
|
||||
#include "io_file.hpp"
|
||||
|
||||
@@ -77,3 +78,5 @@ class FileConvertWriter : public stream::Writer {
|
||||
File file_{};
|
||||
uint64_t bytes_written_{0};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __IO_FILE_H
|
||||
#define __IO_FILE_H
|
||||
|
||||
#include "io.hpp"
|
||||
|
||||
@@ -71,3 +72,5 @@ class FileWriter : public stream::Writer {
|
||||
};
|
||||
|
||||
using RawFileWriter = FileWriter;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __IO_WAVE_H
|
||||
#define __IO_WAVE_H
|
||||
|
||||
#include "io_file.hpp"
|
||||
|
||||
@@ -181,3 +182,5 @@ class WAVFileWriter : public FileWriter {
|
||||
Optional<File::Error> update_header();
|
||||
Optional<File::Error> write_tags();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -85,7 +85,7 @@ static bool touch_update() {
|
||||
case IO::TouchPinsConfig::SensePressure: {
|
||||
const auto z1 = samples.xp - samples.xn;
|
||||
const auto z2 = samples.yp - samples.yn;
|
||||
const auto touch_raw = (z1 > touch::touch_threshold) || (z2 > touch::touch_threshold);
|
||||
const auto touch_raw = (z1 > portapack::touch_threshold) || (z2 > portapack::touch_threshold);
|
||||
touch_debounce = (touch_debounce << 1) | (touch_raw ? 1U : 0U);
|
||||
touch_detected = ((touch_debounce & touch_debounce_mask) == touch_debounce_mask);
|
||||
if (!touch_detected && !touch_cycle) {
|
||||
|
||||
@@ -96,7 +96,6 @@ Continuous (Fox-oring)
|
||||
// Multimon-style stuff:
|
||||
// TODO: DMR detector
|
||||
// TODO: GSM channel detector
|
||||
// TODO: Playdead amnesia and login
|
||||
// TODO: Setup: Play dead by default ? Enable/disable ?
|
||||
|
||||
// Old or low-priority stuff:
|
||||
@@ -106,7 +105,6 @@ Continuous (Fox-oring)
|
||||
// TODO: Check more OOK encoders
|
||||
// BUG (fixed ?): No audio in about when shown second time
|
||||
// TODO: Show MD5 mismatches for modules not found, etc...
|
||||
// TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule
|
||||
// BUG: Description doesn't show up first time going to system>module info (UI drawn on top)
|
||||
// TODO: Two players tic-tac-toe
|
||||
// TODO: Analog TV pong game
|
||||
@@ -141,6 +139,7 @@ Continuous (Fox-oring)
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include "i2cdevmanager.hpp"
|
||||
|
||||
#include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */
|
||||
rffc507x::RFFC507x first_if;
|
||||
@@ -161,6 +160,7 @@ static void event_loop() {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}};
|
||||
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
|
||||
i2cdev::I2CDevManager::setEventDispatcher(&event_dispatcher);
|
||||
system_view.get_navigation_view()->handle_autostart();
|
||||
event_dispatcher.run();
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ TemperatureLogger temperature_logger;
|
||||
|
||||
bool antenna_bias{false};
|
||||
uint32_t bl_tick_counter{0};
|
||||
uint16_t touch_threshold{32};
|
||||
|
||||
void set_antenna_bias(const bool v) {
|
||||
antenna_bias = v;
|
||||
@@ -538,6 +539,11 @@ init_status_t init() {
|
||||
set_cpu_clock_speed();
|
||||
|
||||
if (persistent_memory::config_lcd_inverted_mode()) display.set_inverted(true);
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
*/
|
||||
touch_threshold = portapack::persistent_memory::touchscreen_threshold();
|
||||
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(0, ui::bitmap_icon_memory);
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __PORTAPACK_H
|
||||
#define __PORTAPACK_H
|
||||
|
||||
#include "portapack_io.hpp"
|
||||
|
||||
@@ -67,6 +68,7 @@ extern TransmitterModel transmitter_model;
|
||||
|
||||
extern uint32_t bl_tick_counter;
|
||||
extern bool antenna_bias;
|
||||
extern uint16_t touch_threshold;
|
||||
|
||||
extern TemperatureLogger temperature_logger;
|
||||
|
||||
@@ -85,3 +87,5 @@ Backlight* backlight();
|
||||
extern bool async_tx_enabled; // this is for serial tx things, globally
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -36,14 +36,6 @@ namespace touch {
|
||||
|
||||
using sample_t = uint16_t;
|
||||
|
||||
constexpr sample_t sample_max = 1023;
|
||||
|
||||
// If you have a dead bottom-left corner try to increase the sensitivity,
|
||||
// but look for flickering touch indicator in the Buttons test screen
|
||||
// in which case decrease sensitivity to avoid killing backlight timeout
|
||||
constexpr sample_t touch_sensitivity = 32;
|
||||
constexpr sample_t touch_threshold = sample_max / touch_sensitivity;
|
||||
|
||||
struct Samples {
|
||||
sample_t xp;
|
||||
sample_t xn;
|
||||
|
||||
@@ -4,14 +4,39 @@
|
||||
#include "file_path.hpp"
|
||||
#include "ui_standalone_view.hpp"
|
||||
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
|
||||
|
||||
// iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info.
|
||||
/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback) {
|
||||
// iterates over all possible ext apps-s, and if it is runnable on the current system, it'll call the callback, and pass minimal info. used to print to console, and for autostart setting's app list. where the minimal info is enough
|
||||
// please keep in sync with load_external_items
|
||||
/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback, bool module_included) {
|
||||
if (!callback) return;
|
||||
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
|
||||
if (dev && module_included) {
|
||||
auto device_info = dev->readDeviceInfo();
|
||||
|
||||
if (device_info.has_value()) {
|
||||
for (uint32_t i = 0; i < device_info->application_count; i++) {
|
||||
auto appInfo = dev->getStandaloneAppInfo(i);
|
||||
if (appInfo.has_value() == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
AppInfoConsole appInfoConsole = {reinterpret_cast<char*>(&appInfo->app_name[0]), reinterpret_cast<char*>(&appInfo->app_name[0]), appInfo->menu_location};
|
||||
callback(appInfoConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sd_card::status() != sd_card::Status::Mounted)
|
||||
return;
|
||||
|
||||
@@ -33,18 +58,16 @@ namespace ui {
|
||||
continue;
|
||||
|
||||
bool versionMatches = VERSION_MD5 == application_information.app_version;
|
||||
if (!versionMatches) continue;
|
||||
// here the app is startable and good.
|
||||
std::string appshortname = filePath.filename().string();
|
||||
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
|
||||
// Remove the ".ppma" suffix
|
||||
appshortname = appshortname.substr(0, appshortname.size() - 5);
|
||||
|
||||
if (versionMatches) {
|
||||
std::string appshortname = filePath.filename().string();
|
||||
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
|
||||
// Remove the ".ppma" suffix
|
||||
appshortname = appshortname.substr(0, appshortname.size() - 5);
|
||||
}
|
||||
AppInfoConsole appInfoConsole = {appshortname.c_str(), reinterpret_cast<char*>(&application_information.app_name[0]), application_information.menu_location};
|
||||
callback(appInfoConsole);
|
||||
}
|
||||
AppInfoConsole info{
|
||||
.appCallName = appshortname.c_str(),
|
||||
.appFriendlyName = reinterpret_cast<char*>(&application_information.app_name[0]),
|
||||
.appLocation = application_information.menu_location};
|
||||
callback(info);
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) {
|
||||
@@ -61,21 +84,16 @@ namespace ui {
|
||||
if (!readResult)
|
||||
continue;
|
||||
|
||||
if (application_information.header_version < CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
// here the app is startable and good.
|
||||
std::string appshortname = filePath.filename().string();
|
||||
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppmp") {
|
||||
// Remove the ".ppmp" suffix
|
||||
appshortname = appshortname.substr(0, appshortname.size() - 5);
|
||||
}
|
||||
AppInfoConsole info{
|
||||
.appCallName = appshortname.c_str(),
|
||||
.appFriendlyName = reinterpret_cast<char*>(&application_information.app_name[0]),
|
||||
.appLocation = application_information.menu_location};
|
||||
|
||||
callback(info);
|
||||
AppInfoConsole appInfoConsole = {appshortname.c_str(), reinterpret_cast<char*>(&application_information.app_name[0]), application_information.menu_location};
|
||||
callback(appInfoConsole);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +102,59 @@ namespace ui {
|
||||
|
||||
std::vector<GridItem> external_apps;
|
||||
|
||||
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
|
||||
if (dev) {
|
||||
auto device_info = dev->readDeviceInfo();
|
||||
|
||||
if (device_info.has_value()) {
|
||||
for (uint32_t i = 0; i < device_info->application_count; i++) {
|
||||
auto appInfo = dev->getStandaloneAppInfo(i);
|
||||
if (appInfo.has_value() == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appInfo->menu_location != app_location) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appInfo->header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
GridItem gridItem = {};
|
||||
gridItem.text = reinterpret_cast<char*>(&appInfo->app_name[0]);
|
||||
|
||||
gridItem.color = Color((uint16_t)appInfo->icon_color);
|
||||
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, appInfo, i]() {
|
||||
auto dev2 = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
|
||||
if (dev2) {
|
||||
auto app_image = reinterpret_cast<uint8_t*>(portapack::memory::map::m4_code.end() - appInfo->binary_size);
|
||||
for (size_t j = 0; j < appInfo->binary_size; j += 128) {
|
||||
auto segment = dev2->downloadStandaloneApp(i, j);
|
||||
if (segment.size() != 128) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::copy(segment.begin(), segment.end(), app_image + j);
|
||||
}
|
||||
|
||||
if (!run_module_app(nav, app_image, appInfo->binary_size)) {
|
||||
nav.display_modal("Error", "Unable to run downloaded app.");
|
||||
}
|
||||
} else
|
||||
nav.display_modal("Error", "Unable to download app.");
|
||||
};
|
||||
|
||||
external_apps.push_back(gridItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sd_card::status() != sd_card::Status::Mounted)
|
||||
return external_apps;
|
||||
|
||||
@@ -259,6 +330,7 @@ namespace ui {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: implement baseband image support
|
||||
/* static */ bool ExternalItemsMenuLoader::run_standalone_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
File app;
|
||||
|
||||
@@ -266,7 +338,8 @@ namespace ui {
|
||||
if (openError)
|
||||
return false;
|
||||
|
||||
auto app_image = std::make_unique<uint8_t[]>(app.size());
|
||||
// TODO: move this to m4 memory space
|
||||
auto app_image = reinterpret_cast<uint8_t*>(portapack::memory::map::m4_code.end() - app.size());
|
||||
|
||||
// read file in 512 byte chunks
|
||||
for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) {
|
||||
@@ -286,11 +359,25 @@ namespace ui {
|
||||
uint32_t* ptr = reinterpret_cast<uint32_t*>(&app_image[file_read_index * 4]);
|
||||
|
||||
if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
|
||||
*ptr = *ptr - 0xADB10000 + (uint32_t)app_image.get();
|
||||
*ptr = *ptr - 0xADB10000 + (uint32_t)app_image;
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(std::move(app_image));
|
||||
nav.push<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: implement baseband image support
|
||||
/* static */ bool ExternalItemsMenuLoader::run_module_app(ui::NavigationView& nav, uint8_t* app_image, size_t app_size) {
|
||||
for (size_t file_read_index = 0; file_read_index < app_size / 4; file_read_index++) {
|
||||
uint32_t* ptr = reinterpret_cast<uint32_t*>(&app_image[file_read_index * 4]);
|
||||
|
||||
if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
|
||||
*ptr = *ptr - 0xADB10000 + (uint32_t)app_image;
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ class ExternalItemsMenuLoader {
|
||||
ExternalItemsMenuLoader() = delete;
|
||||
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
|
||||
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
|
||||
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback);
|
||||
static bool run_module_app(ui::NavigationView&, uint8_t*, size_t);
|
||||
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback, bool module_included = false);
|
||||
|
||||
private:
|
||||
static std::vector<DynamicBitmap<16, 16>> bitmaps;
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_handwrite.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void HandWriteView::paint(Painter& painter) {
|
||||
_painter = &painter;
|
||||
}
|
||||
|
||||
HandWriteView::HandWriteView(
|
||||
NavigationView& nav,
|
||||
std::string* str,
|
||||
size_t max_length)
|
||||
: TextEntryView(nav, str, max_length) {
|
||||
size_t n;
|
||||
|
||||
// Handwriting alphabet definition here
|
||||
handwriting = &handwriting_unistroke;
|
||||
|
||||
add_children({&button_case});
|
||||
|
||||
const auto button_fn = [this](Button& button) {
|
||||
this->on_button(button);
|
||||
};
|
||||
|
||||
n = 0;
|
||||
for (auto& button : num_buttons) {
|
||||
add_child(&button);
|
||||
button.on_select = button_fn;
|
||||
button.set_parent_rect({static_cast<Coord>(n * 24),
|
||||
static_cast<Coord>(236),
|
||||
24, 28});
|
||||
const std::string label{
|
||||
(char)(n + '0')};
|
||||
button.set_text(label);
|
||||
button.id = n + '0';
|
||||
n++;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
for (auto& button : special_buttons) {
|
||||
add_child(&button);
|
||||
button.on_select = button_fn;
|
||||
button.set_parent_rect({static_cast<Coord>(50 + n * 24),
|
||||
static_cast<Coord>(270),
|
||||
24, 28});
|
||||
const std::string label{
|
||||
(char)(special_chars[n])};
|
||||
button.set_text(label);
|
||||
button.id = special_chars[n];
|
||||
n++;
|
||||
}
|
||||
|
||||
button_case.on_select = [this, &nav](Button&) {
|
||||
if (_lowercase == true) {
|
||||
_lowercase = false;
|
||||
button_case.set_text("LC");
|
||||
} else {
|
||||
_lowercase = true;
|
||||
button_case.set_text("UC");
|
||||
}
|
||||
};
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
if (on_changed)
|
||||
on_changed(_str);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
update_text();
|
||||
}
|
||||
|
||||
bool HandWriteView::on_touch(const TouchEvent event) {
|
||||
if (event.type == ui::TouchEvent::Type::Start) {
|
||||
stroke_index = 0;
|
||||
move_wait = 3;
|
||||
tracing = true;
|
||||
}
|
||||
if (event.type == ui::TouchEvent::Type::End) {
|
||||
tracing = false;
|
||||
guess_letter();
|
||||
}
|
||||
if (event.type == ui::TouchEvent::Type::Move) {
|
||||
if (tracing)
|
||||
current_pos = event.point;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandWriteView::clear_zone(const Color color, const bool flash) {
|
||||
display.fill_rectangle(
|
||||
{{0, 32}, {240, 216}},
|
||||
color);
|
||||
if (flash) {
|
||||
flash_timer = 8;
|
||||
} else {
|
||||
// Draw grid
|
||||
_painter->draw_rectangle(
|
||||
{{0, 32}, {80, 216}},
|
||||
Color::grey());
|
||||
_painter->draw_rectangle(
|
||||
{{80, 32}, {80, 216}},
|
||||
Color::grey());
|
||||
_painter->draw_rectangle(
|
||||
{{160, 32}, {80, 216}},
|
||||
Color::grey());
|
||||
_painter->draw_rectangle(
|
||||
{{0, 104}, {240, 72}},
|
||||
Color::grey());
|
||||
}
|
||||
}
|
||||
|
||||
void HandWriteView::guess_letter() {
|
||||
uint32_t symbol, match, count, stroke_idx, stroke_data;
|
||||
Condition cond;
|
||||
Direction dir;
|
||||
bool matched;
|
||||
|
||||
// Letter guessing
|
||||
if (stroke_index) {
|
||||
for (symbol = 0; symbol < handwriting->letter_count; symbol++) {
|
||||
count = handwriting->letter[symbol].count;
|
||||
matched = false;
|
||||
if (count) {
|
||||
// We have a count match to do
|
||||
if ((count == 1) && (stroke_index == 1)) matched = true;
|
||||
if ((count == 2) && (stroke_index == 2)) matched = true;
|
||||
if ((count == 3) && (stroke_index > 2)) matched = true;
|
||||
} else {
|
||||
matched = true;
|
||||
}
|
||||
if (matched) {
|
||||
for (match = 0; match < 3; match++) {
|
||||
cond = handwriting->letter[symbol].match[match].cond;
|
||||
dir = handwriting->letter[symbol].match[match].dir;
|
||||
if ((cond != cond_empty) && (dir != dir_empty)) {
|
||||
if (cond == last) {
|
||||
if (stroke_index)
|
||||
stroke_idx = stroke_index - 1;
|
||||
else
|
||||
stroke_idx = 0;
|
||||
} else if (cond == stroke_a)
|
||||
stroke_idx = 0;
|
||||
else if (cond == stroke_b)
|
||||
stroke_idx = 1;
|
||||
else if (cond == stroke_c)
|
||||
stroke_idx = 2;
|
||||
else
|
||||
stroke_idx = 3;
|
||||
if (stroke_idx >= stroke_index) break;
|
||||
stroke_data = stroke_list[stroke_idx];
|
||||
if ((dir & 0xF0) == 0xF0) {
|
||||
if ((dir & 0x0F) != (stroke_data & 0x0F)) break;
|
||||
} else if ((dir & 0x0F) == 0x0F) {
|
||||
if ((dir & 0xF0) != (stroke_data & 0xF0)) break;
|
||||
} else {
|
||||
if (dir != (int32_t)stroke_data) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match == 3)
|
||||
break;
|
||||
else
|
||||
matched = false;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
if (symbol) {
|
||||
if (_lowercase)
|
||||
char_add('a' + symbol - 1);
|
||||
else
|
||||
char_add('A' + symbol - 1);
|
||||
clear_zone(Color::green(), true); // Green flash
|
||||
} else {
|
||||
if (_cursor_pos) {
|
||||
char_delete();
|
||||
clear_zone(Color::yellow(), true); // Yellow flash
|
||||
} else {
|
||||
clear_zone(Color::red(), true); // Red flash
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clear_zone(Color::red(), true); // Red flash
|
||||
}
|
||||
} else {
|
||||
// Short tap is space
|
||||
char_add(' ');
|
||||
clear_zone(Color::green(), true); // Green flash
|
||||
}
|
||||
update_text();
|
||||
stroke_index = 0;
|
||||
}
|
||||
|
||||
void HandWriteView::add_stroke(uint8_t dir) {
|
||||
if (stroke_index < 8) {
|
||||
stroke_list[stroke_index] = dir;
|
||||
stroke_index++;
|
||||
} else {
|
||||
guess_letter();
|
||||
}
|
||||
}
|
||||
|
||||
void HandWriteView::sample_pen() {
|
||||
int16_t diff_x, diff_y;
|
||||
uint8_t dir, dir_ud, dir_lr, stroke_prev;
|
||||
|
||||
draw_cursor();
|
||||
|
||||
if (flash_timer) {
|
||||
if (flash_timer == 1) clear_zone(Color::black(), false);
|
||||
flash_timer--;
|
||||
}
|
||||
|
||||
if (!(sample_skip & 1)) {
|
||||
if (tracing) {
|
||||
if (move_wait) {
|
||||
move_wait--; // ~100ms delay to get rid of jitter from touch start
|
||||
} else {
|
||||
diff_x = current_pos.x() - last_pos.x();
|
||||
diff_y = current_pos.y() - last_pos.y();
|
||||
|
||||
if (current_pos.y() <= 240) {
|
||||
display.fill_rectangle(
|
||||
{{current_pos.x(), current_pos.y()}, {4, 4}},
|
||||
Color::grey());
|
||||
}
|
||||
|
||||
dir = 0; // UL by default
|
||||
if (abs(diff_x) > 7) {
|
||||
if (diff_x > 0)
|
||||
dir |= 0x01; // R
|
||||
} else {
|
||||
dir |= 0x02; // ?
|
||||
}
|
||||
if (abs(diff_y) > 7) {
|
||||
if (diff_y > 0)
|
||||
dir |= 0x10; // D
|
||||
} else {
|
||||
dir |= 0x20; // ?
|
||||
}
|
||||
|
||||
// Need at least two identical directions to validate stroke
|
||||
if ((dir & 0x11) == (dir_prev & 0x11))
|
||||
dir_cnt++;
|
||||
else
|
||||
dir_cnt = 0;
|
||||
dir_prev = dir;
|
||||
|
||||
if (dir_cnt > 1) {
|
||||
dir_cnt = 0;
|
||||
if (stroke_index) {
|
||||
if ((stroke_list[stroke_index - 1] != dir) && (dir != 0x22)) {
|
||||
// Register stroke if different from last one
|
||||
dir_ud = (dir & 0xF0);
|
||||
dir_lr = (dir & 0x0F);
|
||||
stroke_prev = stroke_list[stroke_index - 1];
|
||||
if (dir_ud == 0x20) {
|
||||
// LR changed
|
||||
if ((stroke_prev & 0x0F) != dir_lr) add_stroke(dir);
|
||||
} else if (dir_lr == 0x02) {
|
||||
// UD changed
|
||||
if ((stroke_prev & 0xF0) != dir_ud) add_stroke(dir);
|
||||
} else {
|
||||
// Add direction
|
||||
if (((stroke_prev & 0xF0) == 0x20) && (dir_ud != 0x20)) {
|
||||
// Add UD
|
||||
if ((stroke_prev & 0x0F) == dir_lr) {
|
||||
// Replace totally
|
||||
stroke_list[stroke_index - 1] = dir;
|
||||
} else if (dir_lr == 0x02) {
|
||||
// Merge UD
|
||||
stroke_list[stroke_index - 1] = dir_ud | (stroke_prev & 0x0F);
|
||||
} else {
|
||||
add_stroke(dir);
|
||||
}
|
||||
} else if (((stroke_prev & 0x0F) == 0x02) && (dir_lr != 0x02)) {
|
||||
// Add LR
|
||||
if ((stroke_prev & 0xF0) == dir_ud) {
|
||||
// Replace totally
|
||||
stroke_list[stroke_index - 1] = dir;
|
||||
} else if (dir_ud == 0x20) {
|
||||
// Merge LR
|
||||
stroke_list[stroke_index - 1] = dir_lr | (stroke_prev & 0xF0);
|
||||
} else {
|
||||
add_stroke(dir);
|
||||
}
|
||||
} else {
|
||||
add_stroke(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Register first stroke
|
||||
if (dir != 0x22) add_stroke(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_pos = current_pos;
|
||||
}
|
||||
}
|
||||
|
||||
sample_skip++;
|
||||
}
|
||||
|
||||
void HandWriteView::on_show() {
|
||||
clear_zone(Color::black(), false);
|
||||
}
|
||||
|
||||
void HandWriteView::on_button(Button& button) {
|
||||
char_add(button.id);
|
||||
update_text();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UNISTROKE_H__
|
||||
#define __UNISTROKE_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "unistroke.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class HandWriteView : public TextEntryView {
|
||||
public:
|
||||
HandWriteView(NavigationView& nav, std::string* str, size_t max_length);
|
||||
|
||||
HandWriteView(const HandWriteView&) = delete;
|
||||
HandWriteView(HandWriteView&&) = delete;
|
||||
HandWriteView& operator=(const HandWriteView&) = delete;
|
||||
HandWriteView& operator=(HandWriteView&&) = delete;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_show() override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
private:
|
||||
const char special_chars[5] = {'\'', '.', '?', '!', '='};
|
||||
|
||||
const HandWriting* handwriting{};
|
||||
Painter* _painter{};
|
||||
uint8_t dir_cnt{0};
|
||||
uint8_t dir_prev{0};
|
||||
uint8_t flash_timer{0};
|
||||
bool tracing{false};
|
||||
uint8_t stroke_index{0};
|
||||
uint8_t sample_skip{0}, move_wait{0};
|
||||
uint8_t stroke_list[8];
|
||||
Point start_pos{}, current_pos{}, last_pos{};
|
||||
bool _lowercase = false;
|
||||
|
||||
void sample_pen();
|
||||
void add_stroke(uint8_t dir);
|
||||
void guess_letter();
|
||||
void clear_zone(const Color color, const bool flash);
|
||||
void on_button(Button& button);
|
||||
|
||||
std::array<Button, 10> num_buttons{};
|
||||
std::array<Button, 5> special_buttons{};
|
||||
|
||||
Button button_case{
|
||||
{8, 270, 32, 28},
|
||||
"UC"};
|
||||
|
||||
Button button_ok{
|
||||
{190, 270, 40, 28},
|
||||
"OK"};
|
||||
|
||||
MessageHandlerRegistration message_handler_sample{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->sample_pen();
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UNISTROKE_H__*/
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_loadmodule.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "ff.h"
|
||||
#include "event_m0.hpp"
|
||||
#include "hackrf_gpio.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "ui_rds.hpp"
|
||||
#include "ui_xylos.hpp"
|
||||
#include "ui_epar.hpp"
|
||||
#include "ui_lcr.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "ui_soundboard.hpp"
|
||||
#include "ui_debug.hpp"
|
||||
#include "ui_closecall.hpp"
|
||||
#include "ui_audiotx.hpp"
|
||||
#include "ui_jammer.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void LoadModuleView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
void LoadModuleView::on_show() {
|
||||
char md5_signature[16];
|
||||
uint8_t c;
|
||||
|
||||
memcpy(md5_signature, (const void*)(0x10087FF0), 16);
|
||||
for (c = 0; c < 16; c++) {
|
||||
if (md5_signature[c] != _hash[c]) break;
|
||||
}
|
||||
// text_info.set(to_string_hex(*((unsigned int*)0x10087FF0), 8));
|
||||
|
||||
if (c == 16) {
|
||||
text_info.set("Module already loaded :)");
|
||||
_mod_loaded = true;
|
||||
} else {
|
||||
text_info.set("Loading module");
|
||||
loadmodule();
|
||||
}
|
||||
}
|
||||
|
||||
int LoadModuleView::load_image() {
|
||||
const char magic[6] = {'P', 'P', 'M', ' ', 0x02, 0x00};
|
||||
UINT bw;
|
||||
uint8_t i;
|
||||
uint32_t cnt;
|
||||
char md5sum[16];
|
||||
FILINFO modinfo;
|
||||
FIL modfile;
|
||||
DIR rootdir;
|
||||
FRESULT res;
|
||||
|
||||
// Scan SD card root directory for files with the right MD5 fingerprint at the right location
|
||||
if (f_opendir(&rootdir, "/") == FR_OK) {
|
||||
for (;;) {
|
||||
res = f_readdir(&rootdir, &modinfo);
|
||||
if (res != FR_OK || modinfo.fname[0] == 0) break; // Reached last file, abort
|
||||
// Only care about files with .bin extension
|
||||
if ((!(modinfo.fattrib & AM_DIR)) && (modinfo.fname[9] == 'B') && (modinfo.fname[10] == 'I') && (modinfo.fname[11] == 'N')) {
|
||||
res = f_open(&modfile, modinfo.fname, FA_OPEN_EXISTING | FA_READ);
|
||||
if (res != FR_OK) return 0;
|
||||
// Magic bytes and version check
|
||||
f_read(&modfile, &md5sum, 6, &bw);
|
||||
for (i = 0; i < 6; i++)
|
||||
if (md5sum[i] != magic[i]) break;
|
||||
if (i == 6) {
|
||||
f_lseek(&modfile, 26);
|
||||
f_read(&modfile, &md5sum, 16, &bw);
|
||||
for (i = 0; i < 16; i++)
|
||||
if (md5sum[i] != _hash[i]) break;
|
||||
// f_read can't read more than 512 bytes at a time ?
|
||||
if (i == 16) {
|
||||
f_lseek(&modfile, 512);
|
||||
for (cnt = 0; cnt < 64; cnt++) {
|
||||
if (f_read(&modfile, reinterpret_cast<void*>(portapack::memory::map::m4_code.base() + (cnt * 512)), 512, &bw)) return 0;
|
||||
}
|
||||
f_close(&modfile);
|
||||
f_closedir(&rootdir);
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 13);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
f_close(&modfile);
|
||||
}
|
||||
}
|
||||
f_closedir(&rootdir);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LoadModuleView::loadmodule() {
|
||||
// baseband::shutdown();
|
||||
|
||||
/*EventDispatcher::message_map().register_handler(Message::ID::ReadyForSwitch,
|
||||
[this](Message* const p) {
|
||||
(void)p;*/
|
||||
if (load_image()) {
|
||||
text_infob.set("Module loaded :)");
|
||||
_mod_loaded = true;
|
||||
} else {
|
||||
text_info.set("Module not found :(");
|
||||
_mod_loaded = false;
|
||||
}
|
||||
// }
|
||||
//);
|
||||
}
|
||||
|
||||
LoadModuleView::LoadModuleView(
|
||||
NavigationView& nav,
|
||||
const char* hash,
|
||||
ViewID viewid) {
|
||||
add_children({&text_info,
|
||||
&text_infob,
|
||||
&button_ok});
|
||||
|
||||
_hash = hash;
|
||||
|
||||
button_ok.on_select = [this, &nav, viewid](Button&) {
|
||||
nav.pop();
|
||||
if (_mod_loaded == true) {
|
||||
if (viewid == AudioTX) nav.push<AudioTXView>();
|
||||
if (viewid == Xylos) nav.push<XylosView>();
|
||||
if (viewid == EPAR) nav.push<EPARView>();
|
||||
if (viewid == LCR) nav.push<LCRView>();
|
||||
if (viewid == SoundBoard) nav.push<SoundBoardView>();
|
||||
if (viewid == AnalogAudio) nav.push<AnalogAudioView>();
|
||||
if (viewid == RDS) nav.push<RDSView>();
|
||||
if (viewid == CloseCall) nav.push<CloseCallView>();
|
||||
if (viewid == Receiver) nav.push<ReceiverMenuView>();
|
||||
if (viewid == Jammer) nav.push<JammerView>();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "core_control.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
enum ViewID {
|
||||
Receiver,
|
||||
AudioTX,
|
||||
CloseCall,
|
||||
Xylos,
|
||||
EPAR,
|
||||
LCR,
|
||||
SoundBoard,
|
||||
AnalogAudio,
|
||||
RDS,
|
||||
Jammer
|
||||
};
|
||||
|
||||
class LoadModuleView : public View {
|
||||
public:
|
||||
LoadModuleView(NavigationView& nav, const char* hash, ViewID viewid);
|
||||
void loadmodule();
|
||||
|
||||
void on_show() override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
int load_image(void);
|
||||
const char* _hash;
|
||||
bool _mod_loaded = false;
|
||||
|
||||
Text text_info{
|
||||
{8, 64, 224, 16},
|
||||
"-"};
|
||||
Text text_infob{
|
||||
{8, 64 + 16, 224, 16},
|
||||
"-"};
|
||||
|
||||
Button button_ok{
|
||||
{88, 128, 64, 32},
|
||||
"OK"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
// #include "modules.h"
|
||||
|
||||
#include "bmp_modal_warning.hpp"
|
||||
#include "bmp_splash.hpp"
|
||||
#include "event_m0.hpp"
|
||||
@@ -35,31 +33,21 @@
|
||||
|
||||
#include "ui_about_simple.hpp"
|
||||
#include "ui_adsb_rx.hpp"
|
||||
// #include "ui_adsb_tx.hpp" //moved to ext
|
||||
#include "ui_aprs_rx.hpp"
|
||||
#include "ui_aprs_tx.hpp"
|
||||
#include "ui_bht_tx.hpp"
|
||||
#include "ui_btle_rx.hpp"
|
||||
// #include "ui_coasterp.hpp" //moved to ext
|
||||
#include "ui_debug.hpp"
|
||||
#include "ui_encoders.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "ui_fsk_rx.hpp"
|
||||
#include "ui_iq_trim.hpp"
|
||||
// #include "ui_jammer.hpp" //moved to ext
|
||||
// #include "ui_keyfob.hpp" //moved to ext
|
||||
// #include "ui_lcr.hpp"
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_looking_glass_app.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
// #include "ui_morse.hpp" //moved to ext
|
||||
// #include "ui_nrf_rx.hpp" //moved to ext
|
||||
// #include "ui_numbers.hpp"
|
||||
// #include "ui_nuoptix.hpp"
|
||||
// #include "ui_playdead.hpp"
|
||||
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_pocsag_tx.hpp"
|
||||
#include "ui_rds.hpp"
|
||||
@@ -72,12 +60,9 @@
|
||||
#include "ui_settings.hpp"
|
||||
#include "ui_siggen.hpp"
|
||||
#include "ui_sonde.hpp"
|
||||
// #include "ui_spectrum_painter.hpp" //moved to ext app
|
||||
#include "ui_ss_viewer.hpp"
|
||||
// #include "ui_sstvtx.hpp" //moved to ext
|
||||
// #include "ui_test.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_tone_search.hpp"
|
||||
#include "ui_touchtunes.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_weatherstation.hpp"
|
||||
@@ -88,18 +73,13 @@
|
||||
|
||||
#include "ais_app.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
// #include "analog_tv_app.hpp" //moved to ext
|
||||
// #include "ble_comm_app.hpp"
|
||||
#include "ble_rx_app.hpp"
|
||||
#include "ble_tx_app.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "ert_app.hpp"
|
||||
// #include "gps_sim_app.hpp" //moved to ext
|
||||
// #include "lge_app.hpp" //moved to ext
|
||||
#include "pocsag_app.hpp"
|
||||
#include "replay_app.hpp"
|
||||
#include "soundboard_app.hpp"
|
||||
// #include "tpms_app.hpp" //moved to ext
|
||||
|
||||
#include "core_control.hpp"
|
||||
#include "file.hpp"
|
||||
@@ -142,7 +122,6 @@ static NavigationView::AppMap generate_app_map(const NavigationView::AppList& ap
|
||||
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
|
||||
const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
//{"playdead", "Play dead", HOME, Color::red(), &bitmap_icon_playdead, new ViewFactory<PlayDeadView>()},
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
@@ -154,13 +133,11 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"about", "About", HOME, Color::cyan(), nullptr, new ViewFactory<AboutView>()},
|
||||
/* RX ********************************************************************/
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
|
||||
//{"btle", "BTLE", RX, Color::yellow(), &bitmap_icon_btle, new ViewFactory<BTLERxView>()},
|
||||
//{"blecomm", "BLE Comm", RX, ui::Color::orange(), &bitmap_icon_btle, new ViewFactory<BLECommView>()},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
|
||||
{"ert", "ERT Meter", RX, Color::green(), &bitmap_icon_ert, new ViewFactory<ERTAppView>()},
|
||||
@@ -169,10 +146,9 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
//{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()}, //for JT
|
||||
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
|
||||
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
|
||||
//{"lora", "LoRa", RX, Color::dark_grey(), &bitmap_icon_lora, new ViewFactory<NotImplementedView>()},
|
||||
@@ -183,13 +159,10 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
//{"morse", "Morse", TX, ui::Color::green(), &bitmap_icon_morse, new ViewFactory<MorseView>()}, //moved to ext
|
||||
//{"nuoptixdtmf", "Nuoptix DTMF", TX, ui::Color::green(), &bitmap_icon_nuoptix, new ViewFactory<NuoptixView>()},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
|
||||
{"pocsagtx", "POCSAG TX", TX, ui::Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGTXView>()},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
//{"sstvtx", "SSTV", TX, ui::Color::green(), &bitmap_icon_sstv, new ViewFactory<SSTVTXView>()}, //moved to ext
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
|
||||
@@ -200,7 +173,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
//{"tonesearch", "Tone Search", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<ToneSearchView>()},
|
||||
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_temperature, new ViewFactory<FlashUtilityView>()},
|
||||
@@ -335,15 +308,6 @@ SystemStatusView::SystemStatusView(
|
||||
battery_icon.on_select = [this]() { on_battery_details(); };
|
||||
battery_text.on_select = [this]() { on_battery_details(); };
|
||||
|
||||
button_fake_brightness.on_select = [this](ImageButton&) {
|
||||
set_dirty();
|
||||
pmem::toggle_fake_brightness_level();
|
||||
refresh();
|
||||
if (nullptr != parent()) {
|
||||
parent()->set_dirty(); // The parent of NavigationView shal be the SystemView
|
||||
}
|
||||
};
|
||||
|
||||
button_bias_tee.on_select = [this](ImageButton&) {
|
||||
this->on_bias_tee();
|
||||
};
|
||||
@@ -410,7 +374,6 @@ void SystemStatusView::refresh() {
|
||||
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker);
|
||||
|
||||
if (!pmem::ui_hide_fake_brightness() && !pmem::config_lcd_inverted_mode()) status_icons.add(&button_fake_brightness);
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
batt_was_inited = true;
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
@@ -443,9 +406,6 @@ void SystemStatusView::refresh() {
|
||||
button_converter.set_bitmap(pmem::config_updown_converter() ? &bitmap_icon_downconvert : &bitmap_icon_upconvert);
|
||||
button_converter.set_foreground(pmem::config_converter() ? Theme::getInstance()->fg_red->foreground : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
// Fake Brightness
|
||||
button_fake_brightness.set_foreground((pmem::apply_fake_brightness() & (!pmem::config_lcd_inverted_mode())) ? *Theme::getInstance()->status_active : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -750,31 +710,40 @@ void NavigationView::handle_autostart() {
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
if (!autostart_app.empty()) {
|
||||
bool app_started = false;
|
||||
|
||||
// try innerapp
|
||||
bool started = false;
|
||||
// inner app
|
||||
if (StartAppByName(autostart_app.c_str())) {
|
||||
app_started = true;
|
||||
} else {
|
||||
// try outside app
|
||||
auto external_items = ExternalItemsMenuLoader::load_external_items(app_location_t::HOME, *this);
|
||||
for (const auto& item : external_items) {
|
||||
if (item.text == autostart_app) {
|
||||
item.on_select();
|
||||
app_started = true;
|
||||
break;
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
// ppma
|
||||
|
||||
std::string appwithpath = "/" + apps_dir.string() + "/" + autostart_app + ".ppma";
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
|
||||
std::filesystem::path pth = conv.from_bytes(appwithpath.c_str());
|
||||
if (ui::ExternalItemsMenuLoader::run_external_app(*this, pth)) {
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
// ppmp / standalone
|
||||
appwithpath = "/" + apps_dir.string() + "/" + autostart_app + ".ppmp";
|
||||
pth = conv.from_bytes(appwithpath.c_str());
|
||||
if (ui::ExternalItemsMenuLoader::run_standalone_app(*this, pth)) {
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!app_started) {
|
||||
if (!started) {
|
||||
display_modal(
|
||||
"Notice", "Autostart failed:\n" +
|
||||
autostart_app +
|
||||
"\nupdate sdcard content\n" +
|
||||
"and check if .ppma exists");
|
||||
}
|
||||
}
|
||||
} // autostart end
|
||||
return;
|
||||
}
|
||||
|
||||
/* Helpers **************************************************************/
|
||||
@@ -1017,12 +986,13 @@ BMPView::BMPView(NavigationView& nav)
|
||||
|
||||
void BMPView::paint(Painter&) {
|
||||
if (!portapack::display.drawBMP2({0, 0}, splash_dot_bmp))
|
||||
portapack::display.drawBMP({(240 - 230) / 2, (320 - 50) / 2 - 10}, splash_bmp, (const uint8_t[]){0x29, 0x18, 0x16});
|
||||
portapack::display.drawBMP({0, 16}, splash_bmp, (const uint8_t[]){0x29, 0x18, 0x16});
|
||||
}
|
||||
|
||||
bool BMPView::on_touch(const TouchEvent event) {
|
||||
/* the event thing were resolved by HTotoo, talked here https://discord.com/channels/719669764804444213/956561375155589192/1287756910950486027
|
||||
* the touch screen policy can be better, talked here https://discord.com/channels/719669764804444213/956561375155589192/1198926225897443328
|
||||
* this workaround discussed here: https://discord.com/channels/719669764804444213/1170738202924044338/1295630640158478418
|
||||
*/
|
||||
|
||||
if (!nav_.is_valid()) {
|
||||
@@ -1031,11 +1001,8 @@ bool BMPView::on_touch(const TouchEvent event) {
|
||||
|
||||
switch (event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
handle_pop();
|
||||
return true;
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -279,12 +279,6 @@ class SystemStatusView : public View {
|
||||
Theme::getInstance()->fg_light->foreground,
|
||||
Theme::getInstance()->bg_dark->background};
|
||||
|
||||
ImageButton button_fake_brightness{
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_brightness,
|
||||
*Theme::getInstance()->status_active,
|
||||
Theme::getInstance()->bg_dark->background};
|
||||
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{0, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_playdead.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void PlayDeadView::focus() {
|
||||
button_seq_entry.focus();
|
||||
}
|
||||
|
||||
void PlayDeadView::paint(Painter& painter) {
|
||||
if (persistent_memory::config_login()) {
|
||||
// Blank the whole display
|
||||
painter.fill_rectangle(
|
||||
display.screen_rect(),
|
||||
style().background);
|
||||
}
|
||||
}
|
||||
|
||||
PlayDeadView::PlayDeadView(NavigationView& nav) {
|
||||
rtc::RTC datetime;
|
||||
|
||||
persistent_memory::set_playing_dead(0x5920C1DF); // Enable
|
||||
|
||||
add_children({
|
||||
&text_playdead1,
|
||||
&text_playdead2,
|
||||
&text_playdead3,
|
||||
&button_seq_entry,
|
||||
});
|
||||
|
||||
// Seed from RTC
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
text_playdead2.set("0x" + to_string_hex(lfsr_iterate(datetime.second()), 6) + "00");
|
||||
|
||||
text_playdead3.hidden(true);
|
||||
|
||||
button_seq_entry.on_dir = [this](Button&, KeyEvent key) {
|
||||
sequence = (sequence << 3) | (static_cast<std::underlying_type<KeyEvent>::type>(key) + 1);
|
||||
return true;
|
||||
};
|
||||
|
||||
button_seq_entry.on_select = [this, &nav](Button&) {
|
||||
if (sequence == persistent_memory::playdead_sequence()) {
|
||||
persistent_memory::set_playing_dead(0x82175E23); // Disable
|
||||
if (persistent_memory::config_login()) {
|
||||
text_playdead3.hidden(false);
|
||||
} else {
|
||||
nav.pop();
|
||||
nav.push<SystemMenuView>();
|
||||
}
|
||||
} else {
|
||||
sequence = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_PLAYDEAD_H__
|
||||
#define __UI_PLAYDEAD_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class PlayDeadView : public View {
|
||||
public:
|
||||
PlayDeadView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
uint32_t sequence = 0;
|
||||
|
||||
Text text_playdead1{
|
||||
{6 * 8, 7 * 16, 14 * 8, 16},
|
||||
"\x46irmwa"
|
||||
"re "
|
||||
"er\x72o\x72"};
|
||||
Text text_playdead2{
|
||||
{6 * 8, 9 * 16, 16 * 8, 16},
|
||||
""};
|
||||
Text text_playdead3{
|
||||
{6 * 8, 12 * 16, 16 * 8, 16},
|
||||
"Please reset"};
|
||||
|
||||
Button button_seq_entry{
|
||||
{240, 0, 1, 1},
|
||||
""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_PLAYDEAD_H__*/
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_H
|
||||
#define __USB_SERIAL_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -54,3 +55,5 @@ class USBSerial {
|
||||
};
|
||||
|
||||
} // namespace portapack
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_CDC_H
|
||||
#define __USB_SERIAL_CDC_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -44,3 +45,5 @@ usb_request_status_t usb_set_line_coding_request(usb_endpoint_t* const endpoint,
|
||||
#endif
|
||||
|
||||
void setup_usb_serial_controller(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_DESCRIPTOR_H
|
||||
#define __USB_SERIAL_DESCRIPTOR_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -42,3 +43,5 @@ extern uint8_t* usb_descriptor_strings[];
|
||||
#define USB_WCID_VENDOR_REQ 0x19
|
||||
extern uint8_t wcid_string_descriptor[];
|
||||
extern uint8_t wcid_feature_descriptor[];
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_DEVICE_TO_HOST_H
|
||||
#define __USB_SERIAL_DEVICE_TO_HOST_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -56,3 +57,5 @@ size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_ENDPOINTS_H
|
||||
#define __USB_SERIAL_ENDPOINTS_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -49,3 +50,5 @@ extern USB_DECLARE_QUEUE(usb_endpoint_bulk_in);
|
||||
|
||||
extern usb_endpoint_t usb_endpoint_bulk_out;
|
||||
extern USB_DECLARE_QUEUE(usb_endpoint_bulk_out);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_EVENT_H
|
||||
#define __USB_SERIAL_EVENT_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -32,3 +33,5 @@ void on_channel_closed(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_HOST_TO_DEVICE_H
|
||||
#define __USB_SERIAL_HOST_TO_DEVICE_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -31,3 +32,5 @@ void reset_transfer_queues();
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
void schedule_host_to_device_transfer();
|
||||
void complete_host_to_device_transfer();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "performance_counter.hpp"
|
||||
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "i2c_device_to_host.h"
|
||||
#include "chprintf.h"
|
||||
#include "chqueues.h"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
@@ -59,6 +60,7 @@
|
||||
#define palOutputPad(port, pad) (LPC_GPIO->DIR[(port)] |= 1 << (pad))
|
||||
|
||||
static EventDispatcher* _eventDispatcherInstance = NULL;
|
||||
static bool shell_i2c_created = false;
|
||||
static EventDispatcher* getEventDispatcherInstance() {
|
||||
return _eventDispatcherInstance;
|
||||
}
|
||||
@@ -1079,8 +1081,8 @@ static void cmd_sendpocsag(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
uint64_t addr = atol(argv[0]);
|
||||
int msglen = atoi(argv[1]); // without minimum limit, since addr only don't send anything
|
||||
if (msglen > 30 || msglen < 0) {
|
||||
chprintf(chp, "error, msglen max is 30\r\n");
|
||||
if (msglen > 80 || msglen < 0) {
|
||||
chprintf(chp, "error, msglen max is 80\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1120,7 +1122,7 @@ static void cmd_sendpocsag(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t msg[31] = {0};
|
||||
uint8_t msg[81] = {0};
|
||||
if (msglen > 0) {
|
||||
chprintf(chp, "send %d bytes\r\n", msglen);
|
||||
do {
|
||||
@@ -1222,7 +1224,19 @@ static const ShellConfig shell_cfg1 = {
|
||||
(BaseSequentialStream*)&SUSBD1,
|
||||
commands};
|
||||
|
||||
static const ShellConfig shell_cfg2 = {
|
||||
(BaseSequentialStream*)&I2CD1,
|
||||
commands};
|
||||
|
||||
void create_shell(EventDispatcher* evtd) {
|
||||
_eventDispatcherInstance = evtd;
|
||||
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO + 10);
|
||||
}
|
||||
|
||||
extern "C" void create_shell_i2c(EventDispatcher* evtd) {
|
||||
if (shell_i2c_created) return;
|
||||
shell_i2c_created = true;
|
||||
init_i2c_shell_driver(&I2CD1);
|
||||
_eventDispatcherInstance = evtd;
|
||||
shellCreate(&shell_cfg2, SHELL_WA_SIZE, NORMALPRIO + 10);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __USB_SERIAL_SHELL_H
|
||||
#define __USB_SERIAL_SHELL_H
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -29,3 +30,13 @@
|
||||
class EventDispatcher;
|
||||
|
||||
void create_shell(EventDispatcher* evtd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void create_shell_i2c(EventDispatcher* evtd);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -424,13 +424,6 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PNFM nfm_audio)
|
||||
|
||||
#### No op
|
||||
#
|
||||
#set(MODE_CPPSRC
|
||||
# proc_noop.cpp
|
||||
#)
|
||||
#DeclareTargets(PNOP no_operation)
|
||||
|
||||
### OOK
|
||||
|
||||
set(MODE_CPPSRC
|
||||
@@ -438,12 +431,6 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(POOK ook)
|
||||
|
||||
#### POCSAG RX
|
||||
#
|
||||
#set(MODE_CPPSRC
|
||||
# proc_pocsag.cpp
|
||||
#)
|
||||
#DeclareTargets(PPOC pocsag)
|
||||
|
||||
### POCSAG2 RX
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
|
||||
#ifndef __FPROTO_Bresser3ch_H__
|
||||
#define __FPROTO_Bresser3ch_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define BRESSER_V0_DATA 36
|
||||
#define BRESSER_V0_DATA_AND_TAIL 52
|
||||
#define BRESSER_V1_DATA 40
|
||||
|
||||
typedef enum {
|
||||
Bresser3chDecoderStepReset = 0,
|
||||
Bresser3chDecoderStepV0SaveDuration,
|
||||
Bresser3chDecoderStepV0CheckDuration,
|
||||
Bresser3chDecoderStepV0TailCheckDuration,
|
||||
Bresser3chDecoderStepV1PreambleDn,
|
||||
Bresser3chDecoderStepV1PreambleUp,
|
||||
Bresser3chDecoderStepV1SaveDuration,
|
||||
Bresser3chDecoderStepV1CheckDuration,
|
||||
} Bresser3chDecoderStepV1;
|
||||
|
||||
class FProtoWeatheBresser3CH : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatheBresser3CH() {
|
||||
sensorType = FPW_Bresser3CH;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Bresser3chDecoderStepReset:
|
||||
if (level && DURATION_DIFF(duration, te_short * 3) < te_delta) {
|
||||
te_last = duration;
|
||||
parser_step = Bresser3chDecoderStepV1PreambleDn;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if ((!level) && duration >= te_long) {
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0SaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
if (decode_count_bit < BRESSER_V0_DATA) {
|
||||
parser_step = Bresser3chDecoderStepV0CheckDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepV0TailCheckDuration;
|
||||
}
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0CheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
if (DURATION_DIFF(duration, te_short * 2) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_short * 4) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0TailCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >= te_long) {
|
||||
if (decode_count_bit == BRESSER_V0_DATA_AND_TAIL &&
|
||||
ws_protocol_bresser_3ch_check_v0()) {
|
||||
decode_count_bit = BRESSER_V0_DATA;
|
||||
sensorType = FPW_Bresser3CH;
|
||||
// ws_protocol_bresser_3ch_extract_data_v0();
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else if (
|
||||
decode_count_bit < BRESSER_V0_DATA_AND_TAIL &&
|
||||
DURATION_DIFF(te_last, te_short) < te_delta &&
|
||||
DURATION_DIFF(duration, te_short * 2) < te_delta) {
|
||||
decode_count_bit++;
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1PreambleDn:
|
||||
if ((!level) && DURATION_DIFF(duration, te_short_v1 * 3) < te_delta) {
|
||||
if (DURATION_DIFF(te_last, te_short_v1 * 12) < te_delta * 2) {
|
||||
// End of sync after 4*750 (12*250) high values, start reading the message
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepV1PreambleUp;
|
||||
}
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1PreambleUp:
|
||||
if (level && DURATION_DIFF(duration, te_short_v1 * 3) < te_delta) {
|
||||
te_last = te_last + duration;
|
||||
parser_step = Bresser3chDecoderStepV1PreambleDn;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1SaveDuration:
|
||||
if (decode_count_bit == BRESSER_V1_DATA) {
|
||||
if (ws_protocol_bresser_3ch_check_v1()) {
|
||||
// ws_protocol_bresser_3ch_extract_data_v1(&instance->generic);
|
||||
sensorType = FPW_Bresser3CH_V1;
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Bresser3chDecoderStepV1CheckDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1CheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short_v1) < te_delta && DURATION_DIFF(duration, te_long_v1) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(te_last, te_long_v1) < te_delta && DURATION_DIFF(duration, te_short_v1) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 475;
|
||||
uint32_t te_long = 3900;
|
||||
uint32_t te_delta = 150;
|
||||
|
||||
uint32_t te_short_v1 = 250;
|
||||
uint32_t te_long_v1 = 500;
|
||||
|
||||
bool ws_protocol_bresser_3ch_check_v0() {
|
||||
if (!decode_data) return false;
|
||||
// No CRC, so better sanity checks here
|
||||
if (((decode_data >> 8) & 0x0f) != 0x0f) return false; // separator not 0xf
|
||||
if (((decode_data >> 28) & 0xff) == 0xff) return false; // ID only ones?
|
||||
if (((decode_data >> 28) & 0xff) == 0x00) return false; // ID only zeroes?
|
||||
if (((decode_data >> 25) & 0x0f) == 0x0f) return false; // flags only ones?
|
||||
if (((decode_data >> 25) & 0x0f) == 0x00) return false; // flags only zeroes?
|
||||
if (((decode_data >> 12) & 0x0fff) == 0x0fff)
|
||||
return false; // temperature maxed out?
|
||||
if ((decode_data & 0xff) < 20)
|
||||
return false; // humidity percentage less than 20?
|
||||
if ((decode_data & 0xff) > 95)
|
||||
return false; // humidity percentage more than 95?
|
||||
return true;
|
||||
}
|
||||
bool ws_protocol_bresser_3ch_check_v1() {
|
||||
if (!decode_data) return false;
|
||||
|
||||
uint8_t sum = (((decode_data >> 32) & 0xff) +
|
||||
((decode_data >> 24) & 0xff) +
|
||||
((decode_data >> 16) & 0xff) +
|
||||
((decode_data >> 8) & 0xff)) &
|
||||
0xff;
|
||||
|
||||
return (decode_data & 0xff) == sum;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
#ifndef __FPROTO_VAUNO_EN8822_H__
|
||||
#define __FPROTO_VAUNO_EN8822_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
VaunoEN8822CDecoderStepReset = 0,
|
||||
VaunoEN8822CDecoderStepSaveDuration,
|
||||
VaunoEN8822CDecoderStepCheckDuration,
|
||||
} VaunoEN8822CDecoderStep;
|
||||
|
||||
class FProtoWeatherVaunoEN8822 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherVaunoEN8822() {
|
||||
sensorType = FPW_Vauno_EN8822;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case VaunoEN8822CDecoderStepReset:
|
||||
if ((!level) && DURATION_DIFF(duration, te_long * 4) < te_delta) {
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case VaunoEN8822CDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = VaunoEN8822CDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case VaunoEN8822CDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
if (DURATION_DIFF(duration, te_long * 2) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
} else if (DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
} else if (DURATION_DIFF(duration, te_long * 4) < te_delta) {
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
if (decode_count_bit == min_count_bit_for_found && ws_protocol_vauno_en8822c_check()) {
|
||||
// ws_protocol_vauno_en8822c_extract_data(&instance->generic);
|
||||
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
} else if (decode_count_bit == 1) {
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 1940;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 42;
|
||||
|
||||
bool ws_protocol_vauno_en8822c_check() {
|
||||
if (!decode_data) return false;
|
||||
// The sum of all nibbles should match the last 6 bits
|
||||
uint8_t sum = 0;
|
||||
for (uint8_t i = 6; i <= 38; i += 4) {
|
||||
sum += ((decode_data >> i) & 0x0f);
|
||||
}
|
||||
return sum != 0 && (sum & 0x3f) == (decode_data & 0x3f);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -29,6 +29,8 @@ So include here the .hpp, and add a new element to the protos vector in the cons
|
||||
#include "w-acurite5in1.hpp"
|
||||
#include "w-emose601x.hpp"
|
||||
#include "w-solight_te44.hpp"
|
||||
#include "w-bresser_3ch.hpp"
|
||||
#include "w-vauno_en8822.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -66,10 +68,13 @@ class WeatherProtos : public FProtoListGeneral {
|
||||
protos[FPW_Acurite5in1] = new FProtoWeatherAcurite5in1();
|
||||
protos[FPW_EmosE601x] = new FProtoWeatherEmosE601x();
|
||||
protos[FPW_SolightTE44] = new FProtoWeatherSolightTE44();
|
||||
protos[FPW_Bresser3CH] = new FProtoWeatheBresser3CH();
|
||||
protos[FPW_Bresser3CH_V1] = nullptr; // done by FProtoWeatheBresser3CH
|
||||
protos[FPW_Vauno_EN8822] = new FProtoWeatherVaunoEN8822();
|
||||
|
||||
// set callback for them
|
||||
for (uint8_t i = 0; i < FPW_COUNT; ++i) {
|
||||
protos[i]->setCallback(callbackTarget);
|
||||
if (protos[i] != NULL) protos[i]->setCallback(callbackTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ enum FPROTO_WEATHER_SENSOR {
|
||||
FPW_Acurite5in1 = 21,
|
||||
FPW_EmosE601x = 22,
|
||||
FPW_SolightTE44 = 23,
|
||||
FPW_Bresser3CH = 24,
|
||||
FPW_Bresser3CH_V1 = 25,
|
||||
FPW_Vauno_EN8822 = 26,
|
||||
FPW_COUNT // this must be the last
|
||||
};
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "proc_noop.hpp"
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void NOOPProcessor::execute(const buffer_c8_t& buffer) {
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
buffer.p[i] = {0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<NOOPProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PROC_NOOP_H__
|
||||
#define __PROC_NOOP_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
|
||||
class NOOPProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
private:
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{1536000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,539 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu)
|
||||
* Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com)
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2016 Kyle Reed
|
||||
*
|
||||
* 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 "proc_pocsag.hpp"
|
||||
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
// Get 24kHz audio
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
|
||||
auto audio = demod.execute(channel_out, audio_buffer);
|
||||
|
||||
// If squelching, check for audio before smoothing because smoothing
|
||||
// causes the squelch noise detection to fail. Likely because squelch
|
||||
// looks for HF noise and smoothing is basically a lowpass filter.
|
||||
// NB: Squelch in this processor is only for the the audio output.
|
||||
// Squelching will likely drop data "noise" and break processing.
|
||||
if (squelch_.enabled()) {
|
||||
bool has_audio = squelch_.execute(audio);
|
||||
squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0);
|
||||
}
|
||||
|
||||
smooth.Process(audio.p, audio.count);
|
||||
processDemodulatedSamples(audio.p, 16);
|
||||
extractFrames();
|
||||
|
||||
samples_processed += buffer.count;
|
||||
if (samples_processed >= stat_update_threshold) {
|
||||
send_stats();
|
||||
samples_processed = 0;
|
||||
}
|
||||
|
||||
// Clear the output before sending to audio chip.
|
||||
// Only clear the audio buffer when there hasn't been any audio for a while.
|
||||
if (squelch_.enabled() && squelch_history == 0) {
|
||||
for (size_t i = 0; i < audio.count; ++i) {
|
||||
audio.p[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::OnDataWord(uint32_t word, int pos) {
|
||||
packet.set(pos, word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
||||
if (len > 0) {
|
||||
packet.set_bitrate(baud);
|
||||
packet.set_flag(pocsag::PacketFlag::NORMAL);
|
||||
packet.set_timestamp(Timestamp::now());
|
||||
const POCSAGPacketMessage message(packet);
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void POCSAGProcessor::on_message(const Message* const message) {
|
||||
switch (message->id) {
|
||||
case Message::ID::POCSAGConfigure:
|
||||
configure();
|
||||
break;
|
||||
|
||||
case Message::ID::NBFMConfigure: {
|
||||
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
|
||||
squelch_.set_threshold(config->squelch_level / 100.0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void POCSAGProcessor::configure() {
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
|
||||
const size_t channel_filter_output_fs = channel_filter_input_fs / 2;
|
||||
|
||||
const size_t demod_input_fs = channel_filter_output_fs;
|
||||
|
||||
decim_0.configure(taps_11k0_decim_0.taps);
|
||||
decim_1.configure(taps_11k0_decim_1.taps);
|
||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
|
||||
|
||||
// Smoothing should be roughly sample rate over max baud
|
||||
// 24k / 3.2k = 7.5
|
||||
smooth.SetSize(8);
|
||||
|
||||
// Don't have audio process the stream.
|
||||
audio_output.configure(false);
|
||||
|
||||
// Set up the frame extraction, limits of baud
|
||||
setFrameExtractParams(demod_input_fs, 4000, 300, 32);
|
||||
|
||||
// Mark the class as ready to accept data
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void POCSAGProcessor::send_stats() const {
|
||||
POCSAGStatsMessage message(m_fifo.codeword, m_numCode, m_gotSync, getRate());
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Frame extractraction methods
|
||||
// -----------------------------
|
||||
#define BAUD_STABLE (104)
|
||||
#define MAX_CONSEC_SAME (32)
|
||||
#define MAX_WITHOUT_SINGLE (64)
|
||||
#define MAX_BAD_TRANS (10)
|
||||
|
||||
#define M_SYNC (0x7cd215d8)
|
||||
#define M_NOTSYNC (0x832dea27)
|
||||
|
||||
#define M_IDLE (0x7a89c197)
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
inline int bitsDiff(unsigned long left, unsigned long right) {
|
||||
unsigned long xord = left ^ right;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if ((xord & 0x01) != 0) ++count;
|
||||
xord = xord >> 1;
|
||||
}
|
||||
return (count);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::initFrameExtraction() {
|
||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||
m_lastStableSymbolLen_1024 = m_minSymSamples_1024;
|
||||
|
||||
m_badTransitions = 0;
|
||||
m_bitsStart = 0;
|
||||
m_bitsEnd = 0;
|
||||
m_inverted = false;
|
||||
|
||||
resetVals();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::resetVals() {
|
||||
// Reset the parameters
|
||||
// --------------------
|
||||
m_goodTransitions = 0;
|
||||
m_badTransitions = 0;
|
||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||
m_shortestGoodTrans_1024 = m_maxSymSamples_1024;
|
||||
m_valMid = 0;
|
||||
|
||||
// And reset the counts
|
||||
// --------------------
|
||||
m_lastTransPos_1024 = 0;
|
||||
m_lastBitPos_1024 = 0;
|
||||
m_lastSample = 0;
|
||||
m_sampleNo = 0;
|
||||
m_nextBitPos_1024 = m_maxSymSamples_1024;
|
||||
m_nextBitPosInt = (long)m_nextBitPos_1024;
|
||||
|
||||
// Extraction
|
||||
m_fifo.numBits = 0;
|
||||
m_gotSync = false;
|
||||
m_numCode = 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud, long a_minBaud, long maxRunOfSameValue) {
|
||||
m_samplesPerSec = a_samplesPerSec;
|
||||
m_minSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_maxBaud);
|
||||
m_maxSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_minBaud);
|
||||
m_maxRunOfSameValue = maxRunOfSameValue;
|
||||
|
||||
m_shortestGoodTrans_1024 = m_maxSymSamples_1024;
|
||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||
m_lastStableSymbolLen_1024 = m_minSymSamples_1024;
|
||||
|
||||
m_nextBitPos_1024 = m_averageSymbolLen_1024 / 2;
|
||||
m_nextBitPosInt = m_nextBitPos_1024 >> 10;
|
||||
|
||||
initFrameExtraction();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSamples) {
|
||||
bool transition = false;
|
||||
uint32_t samplePos_1024 = 0;
|
||||
uint32_t len_1024 = 0;
|
||||
|
||||
// Loop through the block of data
|
||||
// ------------------------------
|
||||
for (int pos = 0; pos < noOfSamples; ++pos) {
|
||||
m_sample = sampleBuff[pos];
|
||||
m_valMid += (m_sample - m_valMid) / 1024.0f;
|
||||
|
||||
++m_sampleNo;
|
||||
|
||||
// Detect Transition
|
||||
// -----------------
|
||||
transition = !((m_lastSample < m_valMid) ^ (m_sample >= m_valMid)); // use XOR for speed
|
||||
|
||||
// If this is a transition
|
||||
// -----------------------
|
||||
if (transition) {
|
||||
// Calculate samples since last trans
|
||||
// ----------------------------------
|
||||
int32_t fractional_1024 = (int32_t)(((m_sample - m_valMid) * 1024) / (m_sample - m_lastSample));
|
||||
if (fractional_1024 < 0) {
|
||||
fractional_1024 = -fractional_1024;
|
||||
}
|
||||
|
||||
samplePos_1024 = (m_sampleNo << 10) - fractional_1024;
|
||||
len_1024 = samplePos_1024 - m_lastTransPos_1024;
|
||||
m_lastTransPos_1024 = samplePos_1024;
|
||||
|
||||
// If symbol is large enough to be valid
|
||||
// -------------------------------------
|
||||
if (len_1024 > m_minSymSamples_1024) {
|
||||
// Check for shortest good transition
|
||||
// ----------------------------------
|
||||
if ((len_1024 < m_shortestGoodTrans_1024) &&
|
||||
(m_goodTransitions < BAUD_STABLE)) // detect change of symbol size
|
||||
{
|
||||
int32_t fractionOfShortest_1024 = (len_1024 << 10) / m_shortestGoodTrans_1024;
|
||||
|
||||
// If currently at half the baud rate
|
||||
// ----------------------------------
|
||||
if ((fractionOfShortest_1024 > 410) && (fractionOfShortest_1024 < 614)) // 0.4 and 0.6
|
||||
{
|
||||
m_averageSymbolLen_1024 /= 2;
|
||||
m_shortestGoodTrans_1024 = len_1024;
|
||||
}
|
||||
// If currently at the wrong baud rate
|
||||
// -----------------------------------
|
||||
else if (fractionOfShortest_1024 < 768) // 0.75
|
||||
{
|
||||
m_averageSymbolLen_1024 = len_1024;
|
||||
m_shortestGoodTrans_1024 = len_1024;
|
||||
m_goodTransitions = 0;
|
||||
m_lastSingleBitPos_1024 = samplePos_1024 - len_1024;
|
||||
}
|
||||
}
|
||||
|
||||
// Calc the number of bits since events
|
||||
// ------------------------------------
|
||||
int32_t halfSymbol_1024 = m_averageSymbolLen_1024 / 2;
|
||||
int bitsSinceLastTrans = max((uint32_t)1, (len_1024 + halfSymbol_1024) / m_averageSymbolLen_1024);
|
||||
int bitsSinceLastSingle = (((m_sampleNo << 10) - m_lastSingleBitPos_1024) + halfSymbol_1024) / m_averageSymbolLen_1024;
|
||||
|
||||
// Check for single bit
|
||||
// --------------------
|
||||
if (bitsSinceLastTrans == 1) {
|
||||
m_lastSingleBitPos_1024 = samplePos_1024;
|
||||
}
|
||||
|
||||
// If too long since last transition
|
||||
// ---------------------------------
|
||||
if (bitsSinceLastTrans > MAX_CONSEC_SAME) {
|
||||
resetVals();
|
||||
}
|
||||
// If too long sice last single bit
|
||||
// --------------------------------
|
||||
else if (bitsSinceLastSingle > MAX_WITHOUT_SINGLE) {
|
||||
resetVals();
|
||||
} else {
|
||||
// If this is a good transition
|
||||
// ----------------------------
|
||||
int32_t offsetFromExtectedTransition_1024 = len_1024 - (bitsSinceLastTrans * m_averageSymbolLen_1024);
|
||||
if (offsetFromExtectedTransition_1024 < 0) {
|
||||
offsetFromExtectedTransition_1024 = -offsetFromExtectedTransition_1024;
|
||||
}
|
||||
if (offsetFromExtectedTransition_1024 < ((int32_t)m_averageSymbolLen_1024 / 4)) // Has to be within 1/4 of symbol to be good
|
||||
{
|
||||
++m_goodTransitions;
|
||||
uint32_t bitsCount = min((uint32_t)BAUD_STABLE, m_goodTransitions);
|
||||
|
||||
uint32_t propFromPrevious = m_averageSymbolLen_1024 * bitsCount;
|
||||
uint32_t propFromCurrent = (len_1024 / bitsSinceLastTrans);
|
||||
m_averageSymbolLen_1024 = (propFromPrevious + propFromCurrent) / (bitsCount + 1);
|
||||
m_badTransitions = 0;
|
||||
// if ( len < m_shortestGoodTrans ){m_shortestGoodTrans = len;}
|
||||
// Store the old symbol size
|
||||
if (m_goodTransitions >= BAUD_STABLE) {
|
||||
m_lastStableSymbolLen_1024 = m_averageSymbolLen_1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the point of the last bit if not yet stable
|
||||
// -----------------------------------------------
|
||||
if ((m_goodTransitions < BAUD_STABLE) || (m_badTransitions > 0)) {
|
||||
m_lastBitPos_1024 = samplePos_1024 - (m_averageSymbolLen_1024 / 2);
|
||||
}
|
||||
|
||||
// Calculate the exact positiom of the next bit
|
||||
// --------------------------------------------
|
||||
int32_t thisPlusHalfsymbol_1024 = samplePos_1024 + (m_averageSymbolLen_1024 / 2);
|
||||
int32_t lastPlusSymbol = m_lastBitPos_1024 + m_averageSymbolLen_1024;
|
||||
m_nextBitPos_1024 = lastPlusSymbol + ((thisPlusHalfsymbol_1024 - lastPlusSymbol) / 16);
|
||||
|
||||
// Check for bad pos error
|
||||
// -----------------------
|
||||
if (m_nextBitPos_1024 < samplePos_1024) m_nextBitPos_1024 += m_averageSymbolLen_1024;
|
||||
|
||||
// Calculate integer sample after next bit
|
||||
// ---------------------------------------
|
||||
m_nextBitPosInt = (m_nextBitPos_1024 >> 10) + 1;
|
||||
|
||||
} // symbol is large enough to be valid
|
||||
else {
|
||||
// Bad transition, so reset the counts
|
||||
// -----------------------------------
|
||||
++m_badTransitions;
|
||||
if (m_badTransitions > MAX_BAD_TRANS) {
|
||||
resetVals();
|
||||
}
|
||||
}
|
||||
} // end of if transition
|
||||
|
||||
// Reached the point of the next bit
|
||||
// ---------------------------------
|
||||
if (m_sampleNo >= m_nextBitPosInt) {
|
||||
// Everything is good so extract a bit
|
||||
// -----------------------------------
|
||||
if (m_goodTransitions > 20) {
|
||||
// Store value at the center of bit
|
||||
// --------------------------------
|
||||
storeBit();
|
||||
}
|
||||
// Check for long 1 or zero
|
||||
// ------------------------
|
||||
uint32_t bitsSinceLastTrans = ((m_sampleNo << 10) - m_lastTransPos_1024) / m_averageSymbolLen_1024;
|
||||
if (bitsSinceLastTrans > m_maxRunOfSameValue) {
|
||||
resetVals();
|
||||
}
|
||||
|
||||
// Store the point of the last bit
|
||||
// -------------------------------
|
||||
m_lastBitPos_1024 = m_nextBitPos_1024;
|
||||
|
||||
// Calculate the exact point of the next bit
|
||||
// -----------------------------------------
|
||||
m_nextBitPos_1024 += m_averageSymbolLen_1024;
|
||||
|
||||
// Look for the bit after the next bit pos
|
||||
// ---------------------------------------
|
||||
m_nextBitPosInt = (m_nextBitPos_1024 >> 10) + 1;
|
||||
|
||||
} // Reached the point of the next bit
|
||||
|
||||
m_lastSample = m_sample;
|
||||
|
||||
} // Loop through the block of data
|
||||
|
||||
return getNoOfBits();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::storeBit() {
|
||||
if (++m_bitsStart >= BIT_BUF_SIZE) {
|
||||
m_bitsStart = 0;
|
||||
}
|
||||
|
||||
// Calculate the bit value
|
||||
float sample = (m_sample + m_lastSample) / 2;
|
||||
// int32_t sample_1024 = m_sample_1024;
|
||||
bool bit = sample > m_valMid;
|
||||
|
||||
// If buffer not full
|
||||
if (m_bitsStart != m_bitsEnd) {
|
||||
// Decide on output val
|
||||
if (bit) {
|
||||
m_bits[m_bitsStart] = 0;
|
||||
} else {
|
||||
m_bits[m_bitsStart] = 1;
|
||||
}
|
||||
}
|
||||
// Throw away bits if the buffer is full
|
||||
else {
|
||||
if (--m_bitsStart <= -1) {
|
||||
m_bitsStart = BIT_BUF_SIZE - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::extractFrames() {
|
||||
int msgCnt = 0;
|
||||
// While there is unread data in the bits buffer
|
||||
//----------------------------------------------
|
||||
while (getNoOfBits() > 0) {
|
||||
m_fifo.codeword = (m_fifo.codeword << 1) + getBit();
|
||||
m_fifo.numBits++;
|
||||
|
||||
// If number of bits in fifo equals 32
|
||||
//------------------------------------
|
||||
if (m_fifo.numBits >= 32) {
|
||||
// Not got sync
|
||||
// ------------
|
||||
if (!m_gotSync) {
|
||||
if (bitsDiff(m_fifo.codeword, M_SYNC) <= 2) {
|
||||
m_inverted = false;
|
||||
m_gotSync = true;
|
||||
m_numCode = -1;
|
||||
m_fifo.numBits = 0;
|
||||
} else if (bitsDiff(m_fifo.codeword, M_NOTSYNC) <= 2) {
|
||||
m_inverted = true;
|
||||
m_gotSync = true;
|
||||
m_numCode = -1;
|
||||
m_fifo.numBits = 0;
|
||||
} else {
|
||||
// Cause it to load one more bit
|
||||
m_fifo.numBits = 31;
|
||||
}
|
||||
} // Not got sync
|
||||
else {
|
||||
// Increment the word count
|
||||
// ------------------------
|
||||
++m_numCode; // It got set to -1 when a sync was found, now count the 16 words
|
||||
uint32_t val = m_inverted ? ~m_fifo.codeword : m_fifo.codeword;
|
||||
OnDataWord(val, m_numCode);
|
||||
|
||||
// If at the end of a 16 word block
|
||||
// --------------------------------
|
||||
if (m_numCode >= 15) {
|
||||
msgCnt += OnDataFrame(m_numCode + 1, (m_samplesPerSec << 10) / m_lastStableSymbolLen_1024);
|
||||
m_gotSync = false;
|
||||
m_numCode = -1;
|
||||
}
|
||||
m_fifo.numBits = 0;
|
||||
}
|
||||
} // If number of bits in fifo equals 32
|
||||
} // While there is unread data in the bits buffer
|
||||
return msgCnt;
|
||||
} // extractFrames
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
short POCSAGProcessor::getBit() {
|
||||
if (m_bitsEnd != m_bitsStart) {
|
||||
if (++m_bitsEnd >= BIT_BUF_SIZE) {
|
||||
m_bitsEnd = 0;
|
||||
}
|
||||
return m_bits[m_bitsEnd];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::getNoOfBits() {
|
||||
int bits = m_bitsEnd - m_bitsStart;
|
||||
if (bits < 0) {
|
||||
bits += BIT_BUF_SIZE;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
uint32_t POCSAGProcessor::getRate() const {
|
||||
return ((m_samplesPerSec << 10) + 512) / m_lastStableSymbolLen_1024;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int main() {
|
||||
audio::dma::init_audio_out();
|
||||
|
||||
EventDispatcher event_dispatcher{std::make_unique<POCSAGProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu)
|
||||
* Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com)
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PROC_POCSAG_H__
|
||||
#define __PROC_POCSAG_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
|
||||
#include "pocsag_packet.hpp"
|
||||
|
||||
#include "pocsag.hpp"
|
||||
#include "message.hpp"
|
||||
#include "audio_output.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <bitset>
|
||||
using namespace std;
|
||||
|
||||
// Class used to smooth demodulated waveform prior to decoding
|
||||
// -----------------------------------------------------------
|
||||
template <class ValType, class CalcType>
|
||||
class SmoothVals {
|
||||
protected:
|
||||
ValType* m_lastVals; // Previous N values
|
||||
int m_size; // The size N
|
||||
CalcType m_sumVal; // Running sum of lastVals
|
||||
int m_pos; // Current position in last vals ring buffer
|
||||
int m_count; //
|
||||
|
||||
public:
|
||||
SmoothVals()
|
||||
: m_lastVals(NULL), m_size(1), m_sumVal(0), m_pos(0), m_count(0) {
|
||||
m_lastVals = new ValType[m_size];
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
virtual ~SmoothVals() {
|
||||
delete[] m_lastVals;
|
||||
}
|
||||
|
||||
SmoothVals(const SmoothVals<float, float>&) {
|
||||
}
|
||||
|
||||
SmoothVals& operator=(const SmoothVals<float, float>&) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Set size of smoothing
|
||||
// --------------------------------------------------
|
||||
void SetSize(int size) {
|
||||
m_size = std::max(size, 1);
|
||||
m_pos = 0;
|
||||
delete[] m_lastVals;
|
||||
m_lastVals = new ValType[m_size];
|
||||
m_sumVal = 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Get size of smoothing
|
||||
// --------------------------------------------------
|
||||
int Size() { return m_size; }
|
||||
|
||||
// --------------------------------------------------
|
||||
// In place processing
|
||||
// --------------------------------------------------
|
||||
void Process(ValType* valBuff, int numVals) {
|
||||
ValType tmpVal;
|
||||
|
||||
if (m_count > (1024 * 10)) {
|
||||
// Recalculate the sum value occasionaly, stops accumulated errors when using float
|
||||
m_count = 0;
|
||||
m_sumVal = 0;
|
||||
for (int i = 0; i < m_size; ++i) {
|
||||
m_sumVal += (CalcType)m_lastVals[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Use a rolling smoothed value while processing the buffer
|
||||
for (int buffPos = 0; buffPos < numVals; ++buffPos) {
|
||||
m_pos++;
|
||||
if (m_pos >= m_size) {
|
||||
m_pos = 0;
|
||||
}
|
||||
|
||||
m_sumVal -= (CalcType)m_lastVals[m_pos]; // Subtract the oldest value
|
||||
m_lastVals[m_pos] = valBuff[buffPos]; // Store the new value
|
||||
m_sumVal += (CalcType)m_lastVals[m_pos]; // Add on the new value
|
||||
|
||||
tmpVal = (ValType)(m_sumVal / m_size); // Scale by number of values smoothed
|
||||
valBuff[buffPos] = tmpVal;
|
||||
}
|
||||
|
||||
m_count += numVals;
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------
|
||||
// Class to process base band data to pocsag frames
|
||||
// --------------------------------------------------
|
||||
class POCSAGProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
int OnDataFrame(int len, int baud);
|
||||
int OnDataWord(uint32_t word, int pos);
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
static constexpr uint8_t stat_update_interval = 10;
|
||||
static constexpr uint32_t stat_update_threshold =
|
||||
baseband_fs / stat_update_interval;
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
std::array<float, 32> audio{};
|
||||
const buffer_f32_t audio_buffer{
|
||||
audio.data(),
|
||||
audio.size()};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||
dsp::demodulate::FM demod{};
|
||||
SmoothVals<float, float> smooth = {};
|
||||
|
||||
AudioOutput audio_output{};
|
||||
|
||||
bool configured = false;
|
||||
pocsag::POCSAGPacket packet{};
|
||||
|
||||
uint32_t samples_processed = 0;
|
||||
|
||||
void configure();
|
||||
void send_stats() const;
|
||||
|
||||
// ----------------------------------------
|
||||
// Frame extractraction methods and members
|
||||
// ----------------------------------------
|
||||
void initFrameExtraction();
|
||||
struct FIFOStruct {
|
||||
unsigned long codeword;
|
||||
int numBits;
|
||||
};
|
||||
|
||||
#define BIT_BUF_SIZE (64)
|
||||
|
||||
void resetVals();
|
||||
void setFrameExtractParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32);
|
||||
|
||||
int processDemodulatedSamples(float* sampleBuff, int noOfSamples);
|
||||
int extractFrames();
|
||||
|
||||
void storeBit();
|
||||
short getBit();
|
||||
|
||||
int getNoOfBits();
|
||||
uint32_t getRate() const;
|
||||
|
||||
uint32_t m_averageSymbolLen_1024{0};
|
||||
uint32_t m_lastStableSymbolLen_1024{0};
|
||||
|
||||
uint32_t m_samplesPerSec{0};
|
||||
uint32_t m_goodTransitions{0};
|
||||
uint32_t m_badTransitions{0};
|
||||
|
||||
uint32_t m_sampleNo{0};
|
||||
float m_sample{0};
|
||||
float m_valMid{0.0f};
|
||||
float m_lastSample{0.0f};
|
||||
|
||||
uint32_t m_lastTransPos_1024{0};
|
||||
uint32_t m_lastSingleBitPos_1024{0};
|
||||
|
||||
uint32_t m_nextBitPosInt{0}; // Integer rounded up version to save on ops
|
||||
uint32_t m_nextBitPos_1024{0};
|
||||
uint32_t m_lastBitPos_1024{0};
|
||||
|
||||
uint32_t m_shortestGoodTrans_1024{0};
|
||||
uint32_t m_minSymSamples_1024{0};
|
||||
uint32_t m_maxSymSamples_1024{0};
|
||||
uint32_t m_maxRunOfSameValue{0};
|
||||
|
||||
bitset<(size_t)BIT_BUF_SIZE> m_bits{0};
|
||||
long m_bitsStart{0};
|
||||
long m_bitsEnd{0};
|
||||
|
||||
FIFOStruct m_fifo{0, 0};
|
||||
bool m_gotSync{false};
|
||||
int m_numCode{0};
|
||||
bool m_inverted{false};
|
||||
|
||||
FMSquelch squelch_{};
|
||||
uint64_t squelch_history = 0;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_POCSAG_H__*/
|
||||
@@ -19,7 +19,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef __PROC_SPECTRUM_PAINTER_H
|
||||
#define __PROC_SPECTRUM_PAINTER_H
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "baseband_processor.hpp"
|
||||
@@ -46,3 +47,5 @@ class SpectrumPainterProcessor : public BasebandProcessor {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,32 +38,79 @@ void SubGhzDProcessor::execute(const buffer_c8_t& buffer) {
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
threshold = (low_estimate + high_estimate) / 2;
|
||||
int32_t const hysteresis = threshold / 8; // +-12%
|
||||
int16_t re = decim_1_out.p[i].real();
|
||||
int16_t im = decim_1_out.p[i].imag();
|
||||
uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im);
|
||||
|
||||
mag = (mag >> 12); // Decim samples are calculated with saturated gain . (we could also reduce that sat. param at configure time)
|
||||
mag = (mag >> 10);
|
||||
int32_t const ook_low_delta = mag - low_estimate;
|
||||
bool meashl = currentHiLow;
|
||||
if (sig_state == STATE_IDLE) {
|
||||
if (mag > (threshold + hysteresis)) { // just become high
|
||||
meashl = true;
|
||||
sig_state = STATE_PULSE;
|
||||
numg = 0;
|
||||
} else {
|
||||
meashl = false; // still low
|
||||
low_estimate += ook_low_delta / OOK_EST_LOW_RATIO;
|
||||
low_estimate += ((ook_low_delta > 0) ? 1 : -1); // Hack to compensate for lack of fixed-point scaling
|
||||
// Calculate default OOK high level estimate
|
||||
high_estimate = 1.35 * low_estimate; // Default is a ratio of low level
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
} else if (sig_state == STATE_PULSE) {
|
||||
++numg;
|
||||
if (numg > 100) numg = 100;
|
||||
if (mag < (threshold - hysteresis)) {
|
||||
// check if really a bad value
|
||||
if (numg < 3) {
|
||||
// susp
|
||||
sig_state = STATE_GAP;
|
||||
} else {
|
||||
numg = 0;
|
||||
sig_state = STATE_GAP_START;
|
||||
}
|
||||
meashl = false; // low
|
||||
} else {
|
||||
high_estimate += mag / OOK_EST_HIGH_RATIO - high_estimate / OOK_EST_HIGH_RATIO;
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
meashl = true; // still high
|
||||
}
|
||||
} else if (sig_state == STATE_GAP_START) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else if (numg >= 3) {
|
||||
sig_state = STATE_GAP;
|
||||
meashl = false; // gap
|
||||
}
|
||||
} else if (sig_state == STATE_GAP) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
numg = 0;
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else {
|
||||
meashl = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
if (meashl == currentHiLow && currentDuration < 30'000'000) // allow pass 'end' signal
|
||||
{
|
||||
currentDuration += nsPerDecSamp;
|
||||
} else { // called on change, so send the last duration and dir.
|
||||
if (currentDuration >= 30'000'000) sig_state = STATE_IDLE;
|
||||
if (protoList) protoList->feed(currentHiLow, currentDuration / 1000);
|
||||
currentDuration = nsPerDecSamp;
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
}
|
||||
|
||||
cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor.
|
||||
if (cnt > 90'000) {
|
||||
threshold = (tm / cnt) / 2;
|
||||
cnt = 0;
|
||||
tm = 0;
|
||||
if (threshold < 50) threshold = 50;
|
||||
if (threshold > 1700) threshold = 1700;
|
||||
}
|
||||
}
|
||||
|
||||
void SubGhzDProcessor::on_message(const Message* const message) {
|
||||
@@ -75,8 +122,6 @@ void SubGhzDProcessor::configure(const SubGhzFPRxConfigureMessage& message) {
|
||||
// constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor; //unused
|
||||
// constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor; //unused
|
||||
|
||||
modulation = message.modulation; // TODO: add support for FM (currently AM only)
|
||||
|
||||
baseband_fs = message.sampling_rate;
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
nsPerDecSamp = 1'000'000'000 / baseband_fs * 8; // Scaled it due to less array buffer sampes due to /8 decimation. 250 nseg (4Mhz) * 8
|
||||
|
||||
@@ -32,7 +32,14 @@
|
||||
#include "message.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("Os")
|
||||
#include "fprotos/subghzdprotos.hpp"
|
||||
#pragma GCC pop_options
|
||||
|
||||
#define OOK_EST_HIGH_RATIO 3 // Constant for slowness of OOK high level estimator
|
||||
#define OOK_EST_LOW_RATIO 5 // Constant for slowness of OOK low level (noise) estimator (very slow)
|
||||
#define OOK_MAX_HIGH_LEVEL 450000
|
||||
|
||||
class SubGhzDProcessor : public BasebandProcessor {
|
||||
public:
|
||||
@@ -40,9 +47,18 @@ class SubGhzDProcessor : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_PULSE = 1,
|
||||
STATE_GAP_START = 2,
|
||||
STATE_GAP = 3,
|
||||
} sig_state = STATE_IDLE;
|
||||
uint32_t low_estimate = 100;
|
||||
uint32_t high_estimate = 12000;
|
||||
uint32_t min_high_level = 10;
|
||||
uint8_t numg = 0; // count of matched signals to filter spikes
|
||||
size_t baseband_fs = 0; // will be set later by configure message
|
||||
uint32_t nsPerDecSamp = 0;
|
||||
uint8_t modulation = 0;
|
||||
|
||||
/* Array Buffer aux. used in decim0 and decim1 IQ c16 signed data ; (decim0 defines the max length of the array) */
|
||||
std::array<complex16_t, 512> dst{}; // decim0 /4 , 2048/4 = 512 complex I,Q
|
||||
@@ -55,14 +71,10 @@ class SubGhzDProcessor : public BasebandProcessor {
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
|
||||
uint32_t currentDuration = 0;
|
||||
uint32_t threshold = 0x0630; // will overwrite after the first iteration
|
||||
uint32_t threshold = 0x0630;
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
|
||||
// for threshold
|
||||
uint32_t cnt = 0;
|
||||
uint32_t tm = 0;
|
||||
|
||||
FProtoListGeneral* protoList = new SubGhzDProtos(); // holds all the protocols we can parse
|
||||
void configure(const SubGhzFPRxConfigureMessage& message);
|
||||
|
||||
|
||||
@@ -39,32 +39,79 @@ void WeatherProcessor::execute(const buffer_c8_t& buffer) {
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
threshold = (low_estimate + high_estimate) / 2;
|
||||
int32_t const hysteresis = threshold / 8; // +-12%
|
||||
int16_t re = decim_1_out.p[i].real();
|
||||
int16_t im = decim_1_out.p[i].imag();
|
||||
uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im);
|
||||
|
||||
mag = (mag >> 12); // Decim samples are calculated with saturated gain . (we could also reduce that sat. param at configure time)
|
||||
mag = (mag >> 10);
|
||||
int32_t const ook_low_delta = mag - low_estimate;
|
||||
bool meashl = currentHiLow;
|
||||
if (sig_state == STATE_IDLE) {
|
||||
if (mag > (threshold + hysteresis)) { // just become high
|
||||
meashl = true;
|
||||
sig_state = STATE_PULSE;
|
||||
numg = 0;
|
||||
} else {
|
||||
meashl = false; // still low
|
||||
low_estimate += ook_low_delta / OOK_EST_LOW_RATIO;
|
||||
low_estimate += ((ook_low_delta > 0) ? 1 : -1); // Hack to compensate for lack of fixed-point scaling
|
||||
// Calculate default OOK high level estimate
|
||||
high_estimate = 1.35 * low_estimate; // Default is a ratio of low level
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
} else if (sig_state == STATE_PULSE) {
|
||||
++numg;
|
||||
if (numg > 100) numg = 100;
|
||||
if (mag < (threshold - hysteresis)) {
|
||||
// check if really a bad value
|
||||
if (numg < 3) {
|
||||
// susp
|
||||
sig_state = STATE_GAP;
|
||||
} else {
|
||||
numg = 0;
|
||||
sig_state = STATE_GAP_START;
|
||||
}
|
||||
meashl = false; // low
|
||||
} else {
|
||||
high_estimate += mag / OOK_EST_HIGH_RATIO - high_estimate / OOK_EST_HIGH_RATIO;
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
meashl = true; // still high
|
||||
}
|
||||
} else if (sig_state == STATE_GAP_START) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else if (numg >= 3) {
|
||||
sig_state = STATE_GAP;
|
||||
meashl = false; // gap
|
||||
}
|
||||
} else if (sig_state == STATE_GAP) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
numg = 0;
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else {
|
||||
meashl = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
if (meashl == currentHiLow && currentDuration < 30'000'000) // allow pass 'end' signal
|
||||
{
|
||||
currentDuration += nsPerDecSamp;
|
||||
} else { // called on change, so send the last duration and dir.
|
||||
if (currentDuration >= 30'000'000) sig_state = STATE_IDLE;
|
||||
if (protoList) protoList->feed(currentHiLow, currentDuration / 1000);
|
||||
currentDuration = nsPerDecSamp;
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
}
|
||||
|
||||
cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor.
|
||||
if (cnt > 90'000) {
|
||||
threshold = (tm / cnt) / 2;
|
||||
cnt = 0;
|
||||
tm = 0;
|
||||
if (threshold < 50) threshold = 50;
|
||||
if (threshold > 1700) threshold = 1700;
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherProcessor::on_message(const Message* const message) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#ifndef __PROC_WEATHER_H__
|
||||
#define __PROC_WEATHER_H__
|
||||
|
||||
#include <algorithm>
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
@@ -34,15 +35,28 @@
|
||||
|
||||
#include "fprotos/weatherprotos.hpp"
|
||||
|
||||
#define OOK_EST_HIGH_RATIO 3 // Constant for slowness of OOK high level estimator
|
||||
#define OOK_EST_LOW_RATIO 5 // Constant for slowness of OOK low level (noise) estimator (very slow)
|
||||
#define OOK_MAX_HIGH_LEVEL 450000
|
||||
|
||||
class WeatherProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_PULSE = 1,
|
||||
STATE_GAP_START = 2,
|
||||
STATE_GAP = 3,
|
||||
} sig_state = STATE_IDLE;
|
||||
uint32_t low_estimate = 100;
|
||||
uint32_t high_estimate = 12000;
|
||||
uint32_t min_high_level = 10;
|
||||
size_t baseband_fs = 0; // will be set later by configure message.
|
||||
uint32_t nsPerDecSamp = 0;
|
||||
|
||||
uint8_t numg = 0; // count of matched signals to filter spikes
|
||||
/* Array Buffer aux. used in decim0 and decim1 IQ c16 signed data ; (decim0 defines the max length of the array) */
|
||||
std::array<complex16_t, 512> dst{}; // decim0 /4 , 2048/4 = 512 complex I,Q
|
||||
const buffer_c16_t dst_buffer{
|
||||
@@ -58,10 +72,6 @@ class WeatherProcessor : public BasebandProcessor {
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
|
||||
// for threshold
|
||||
uint32_t cnt = 0;
|
||||
uint32_t tm = 0;
|
||||
|
||||
FProtoListGeneral* protoList = new WeatherProtos(); // holds all the protocols we can parse
|
||||
void configure(const SubGhzFPRxConfigureMessage& message);
|
||||
void on_beep_message(const AudioBeepMessage& message);
|
||||
|
||||
@@ -276,7 +276,7 @@ class APRSPacket {
|
||||
if (!is_digits(sub)) {
|
||||
return 0;
|
||||
} else {
|
||||
return std::stoi(sub);
|
||||
return atoi(sub.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user