mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-02 04:49:01 +00:00
Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba5715db0 | |||
| c7e8506ad0 | |||
| 8cb0a91bb5 | |||
| 1bcbefeb96 | |||
| 31031edbd1 | |||
| 7116f92d07 | |||
| df8e79f9e6 | |||
| e80e4e3bfd | |||
| da6c6bb03c | |||
| 75718c79b9 | |||
| 9b263def37 | |||
| c7d88da1a6 | |||
| 7b6f8b271c | |||
| 1b18b3ac45 | |||
| 68fc2a143f | |||
| 1deebaff09 | |||
| d6118c9fc4 | |||
| 44a62aef21 | |||
| 0742fc169d | |||
| 62859f901f | |||
| c6316f5aa6 | |||
| 5f2043d229 | |||
| 074658c4bb | |||
| d641ae5b47 | |||
| bec70b4ee5 | |||
| c4373d1560 | |||
| 1dae2c0d25 | |||
| 658d0c9b3a | |||
| 2f343adf21 | |||
| f7a5f2c437 | |||
| 5a336d5e71 | |||
| 91675e26cb | |||
| 078da8ca16 | |||
| 77260bc68a | |||
| ca143f9788 | |||
| 3fc23354ce | |||
| d48e25167f | |||
| cfe5a6bfe4 | |||
| d77102426a | |||
| 11f4edc892 | |||
| 804fa0d3c4 | |||
| d5f20c45b9 | |||
| 2cba96ff36 | |||
| 7139abb947 | |||
| bf4ed416bd | |||
| 06643df6a5 | |||
| 81752c8d78 | |||
| 0cb8c9200a | |||
| 8e64221384 | |||
| fe1837ec23 | |||
| ee53b28e60 | |||
| 18c986cd76 | |||
| a1c3cbcea9 | |||
| 1135a42932 | |||
| a74d0e5167 | |||
| c5579e25fd | |||
| e8b8f0ca5c | |||
| 98e7116230 | |||
| c916eaf43f | |||
| e3169a3495 | |||
| 4b37f1bb2f | |||
| cb93f2636e | |||
| 596c304b08 | |||
| 63f7be02b6 | |||
| de99afa19b | |||
| 5c4e27ea29 | |||
| 54e4230191 | |||
| f069383c2a | |||
| d3a356d415 | |||
| f0e4fdcbcc | |||
| d5f6f42fae | |||
| 7a89858cc6 | |||
| 15c9a76536 | |||
| d7359a8cd5 | |||
| 50e5bc60ee | |||
| 9385be4f1e | |||
| 9cddab9a5e | |||
| 6e01a1d0dc | |||
| f73cd39d2d | |||
| 0303c658ea | |||
| 94ddb63b1d | |||
| b1aa607a7c | |||
| 509f86c1f7 | |||
| b45ca43e27 | |||
| 47482d1e58 | |||
| 59dfe5b50b | |||
| b1f5023fe1 | |||
| a54b94fe53 | |||
| 45bdabcef9 | |||
| 6cdada1118 | |||
| cffc72c516 | |||
| e53514aa12 | |||
| 775de5ce6f | |||
| 4ff92be23b | |||
| 948d8d947e | |||
| 55c300ac4b | |||
| 29b7a5ee56 | |||
| 2ef9ebd7bd | |||
| 850a79c9bb | |||
| 812f0f8211 | |||
| d00c21adb7 | |||
| 925c1548a8 | |||
| cd1f1bd388 | |||
| 474fe00146 | |||
| 44b2e5ea61 | |||
| 48ed7b1b1a | |||
| 6201be82ea | |||
| fe1d296b48 | |||
| 697876d86a | |||
| b72c765911 | |||
| 2a14888b80 | |||
| 99f1bded4d | |||
| d8e9fad205 | |||
| 0e8dc1567c | |||
| 4ff71dcfca | |||
| ed0f0eb15e | |||
| 193a80edea | |||
| df0c85db8f | |||
| c5738c46d5 | |||
| 0acf7ec2c2 | |||
| dc560ba01f | |||
| d74fd92451 | |||
| bcefa774cd | |||
| 8382eaf45f | |||
| 9f700a30c0 | |||
| d815e6c952 | |||
| e60ec953ab | |||
| 72d1e21fc6 | |||
| e4d42817ee | |||
| 5729bde214 | |||
| ec7a77896a | |||
| 99c26c3b7b | |||
| aa20bff2b9 | |||
| f5e949047d | |||
| afb75af5b1 | |||
| 7f99b1181e | |||
| cec0fe3288 | |||
| ba3114c0b6 | |||
| cc2bb772e5 | |||
| fbf9138410 | |||
| 0559aa2037 | |||
| 0d7072ec5d | |||
| c1f55c0dec | |||
| 464b7dd98d | |||
| 4d429437b5 | |||
| bc39292e72 | |||
| 27f70cb9fe | |||
| e5f80adeb7 | |||
| 5a6f6e8db0 | |||
| 21547e6c55 | |||
| 12cfbf70f0 | |||
| c3971cae32 | |||
| f7c2dde79f | |||
| cd2d810c31 | |||
| ba6202e651 | |||
| 40d64030c8 | |||
| 049a20b878 | |||
| 0e5ae7a6a4 | |||
| c867f98117 | |||
| 54eebedc1f | |||
| 35db368527 | |||
| be691fd278 | |||
| 3db3e78b44 | |||
| a48ecccd7d | |||
| 570775552c | |||
| ecbb232d9c | |||
| 0e59e61461 | |||
| 7da322f926 | |||
| 04392752d1 | |||
| db5fcaba44 | |||
| a8f0c8eac9 | |||
| 6fadd70c1d | |||
| 8d8d457da2 | |||
| 893df7272f | |||
| bb90fdc5fc | |||
| 50859171f4 | |||
| fc1b676ce6 | |||
| 776ac652a8 | |||
| c9c438824d | |||
| 95eb1e6d59 | |||
| 1f32367495 | |||
| 0ed5e6542e | |||
| 7240d45a4f | |||
| 17250304e0 | |||
| 835bc49999 | |||
| a6eb430830 | |||
| 8dd68a0811 | |||
| 9540776afd | |||
| f5d4fce945 | |||
| 8840a6e894 | |||
| 7ef98f7741 | |||
| b41be47c9d | |||
| 1e2bc703b8 | |||
| 9115eadd17 | |||
| 05429d3d15 | |||
| 30c1f9f594 | |||
| f994f8d697 | |||
| e045628dab | |||
| 17cdb34fea | |||
| 9df0f6e9df | |||
| 54e4b535cc | |||
| d6555a7af0 | |||
| 30004a1c3c | |||
| 8d60da0a5a | |||
| 17f30b6f26 | |||
| 3d13ee828e | |||
| 9398b12093 | |||
| 61b82645f7 | |||
| c285084dcd | |||
| e034c6e66e | |||
| bb3ba29d22 | |||
| ad11ab649e | |||
| c76c6bd857 | |||
| 47b770bc3a | |||
| dd8538b8bf | |||
| 03c9b4aba7 | |||
| 56526a5710 | |||
| 2d9fe6cf8a | |||
| 4ec444688f | |||
| 2e4a86d7ec | |||
| 92b622deac | |||
| 2457ba016f | |||
| 4a6c76cc47 | |||
| 7b4662fe4a | |||
| 8fcedb017c | |||
| 7b3ab64930 | |||
| b856b047cc | |||
| ce23c7d882 | |||
| 7eba8fd49b |
@@ -9,7 +9,7 @@ assignees: ''
|
||||
|
||||
----
|
||||
|
||||
(Please try the latest nightly release before submitting this. You can find the latest nightly verison here: https://github.com/eried/portapack-mayhem/releases)
|
||||
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/eried/portapack-mayhem/releases)
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ You can find the list of versions here: https://github.com/eried/portapack-mayhe
|
||||
|
||||
**Affected versions**
|
||||
Please tell us what version you are running.
|
||||
Also please try the latest nightly release before submitting this.
|
||||
If your device is still functional, try the latest nightly release before submitting this.
|
||||
You can find the latest nightly version here https://github.com/eried/portapack-mayhem/releases
|
||||
|
||||
**Were you able to update the firmware before?**
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.5.4
|
||||
v1.6.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.6.0
|
||||
v1.7.0
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ endif()
|
||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
||||
|
||||
add_subdirectory(hackrf/firmware/hackrf_usb)
|
||||
add_subdirectory(hackrf-portapack)
|
||||
|
||||
set(HACKRF_FIRMWARE_DFU_FILENAME hackrf_usb.dfu)
|
||||
set(HACKRF_FIRMWARE_BIN_FILENAME hackrf_usb_ram.bin)
|
||||
|
||||
@@ -39,7 +39,7 @@ The current **stable release** is on the [, fix bugs and [answer issues](https://github.com/eried/portapack-mayhem/issues) or add new functionality. Please check the following [guide](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) with details.
|
||||
|
||||
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/eried/portapack-mayhem/graphs/contributors), so always try colaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/eried/portapack-mayhem/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
|
||||
[](https://github.com/eried/portapack-mayhem/graphs/contributors)
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ COPY ./ /havocsrc
|
||||
|
||||
#Fetch dependencies from APT
|
||||
RUN apt-get update && \
|
||||
apt-get install -y tar wget dfu-util cmake python bzip2 curl python3 python3-yaml && \
|
||||
apt-get install -y tar wget dfu-util cmake python bzip2 lz4 curl python3 python3-yaml && \
|
||||
apt-get -qy autoremove
|
||||
|
||||
#Install current pip from PyPa
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ COPY ./ /havocsrc
|
||||
# Fetch dependencies from APK
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
|
||||
RUN apk update -U
|
||||
RUN apk add --no-cache git tar wget cmake curl bzip2 make
|
||||
RUN apk add --no-cache git tar wget cmake curl bzip2 lz4 make
|
||||
RUN apk add --no-cache dfu-util ccache icu-data-full
|
||||
RUN apk add --no-cache python3 py3-pip py3-yaml
|
||||
RUN apk add --no-cache py3-pyyaml-env-tag
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ WORKDIR /havoc/firmware
|
||||
|
||||
# Fetch dependencies from APT
|
||||
RUN apt-get update && \
|
||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 curl && \
|
||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
|
||||
apt-get -qy autoremove
|
||||
|
||||
#Install current pip from PyPa
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /havoc/firmware
|
||||
# Fetch dependencies from APK
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
|
||||
RUN apk update -U
|
||||
RUN apk add --no-cache git tar wget cmake curl bzip2 make
|
||||
RUN apk add --no-cache git tar wget cmake curl bzip2 lz4 make
|
||||
RUN apk add --no-cache dfu-util ccache icu-data-full
|
||||
RUN apk add --no-cache python3 py3-pip py3-yaml
|
||||
RUN apk add --no-cache py3-pyyaml-env-tag
|
||||
|
||||
@@ -28,6 +28,7 @@ set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
|
||||
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)
|
||||
set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
|
||||
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
|
||||
set(LZ4 lz4)
|
||||
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
|
||||
@@ -155,6 +155,7 @@ set(CPPSRC
|
||||
${COMMON}/ui_widget.cpp
|
||||
${COMMON}/utility.cpp
|
||||
${COMMON}/wm8731.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
app_settings.cpp
|
||||
audio.cpp
|
||||
baseband_api.cpp
|
||||
@@ -230,10 +231,13 @@ set(CPPSRC
|
||||
apps/ui_nrf_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
apps/ui_bht_tx.cpp
|
||||
apps/ui_dfu_menu.cpp
|
||||
apps/ui_coasterp.cpp
|
||||
apps/ui_debug.cpp
|
||||
apps/ui_encoders.cpp
|
||||
apps/ui_fileman.cpp
|
||||
apps/ui_flash_utility.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
apps/ui_freqman.cpp
|
||||
apps/ui_jammer.cpp
|
||||
apps/ui_keyfob.cpp
|
||||
@@ -317,7 +321,10 @@ set(TCSRC)
|
||||
set(TCPPSRC)
|
||||
|
||||
# List ASM source files here
|
||||
set(ASMSRC ${PORTASM})
|
||||
set(ASMSRC
|
||||
${PORTASM}
|
||||
lz4.S
|
||||
)
|
||||
|
||||
set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC}
|
||||
${HALINC} ${PLATFORMINC} ${BOARDINC}
|
||||
|
||||
@@ -166,7 +166,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "AIS Boats RX"; };
|
||||
std::string title() const override { return "AIS RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 162025000;
|
||||
|
||||
@@ -140,10 +140,10 @@ AnalogAudioView::AnalogAudioView(
|
||||
field_lna.set_value(app_settings.lna);
|
||||
field_vga.set_value(app_settings.vga);
|
||||
receiver_model.set_rf_amp(app_settings.rx_amp);
|
||||
field_frequency.set_value(app_settings.rx_frequency);
|
||||
// field_frequency.set_value(app_settings.rx_frequency);
|
||||
receiver_model.set_configuration_without_init(static_cast<ReceiverModel::Mode>(app_settings.modulation), app_settings.step, app_settings.am_config_index, app_settings.nbfm_config_index, app_settings.wfm_config_index, app_settings.squelch);
|
||||
}
|
||||
else field_frequency.set_value(receiver_model.tuning_frequency());
|
||||
field_frequency.set_value(receiver_model.tuning_frequency());
|
||||
|
||||
//Filename Datetime and Frequency
|
||||
record_view.set_filename_date_frequency(true);
|
||||
|
||||
@@ -117,6 +117,7 @@ private:
|
||||
{ " 2m ", 2000000 },
|
||||
{ " 1m ", 1000000 },
|
||||
{ "500k", 500000 },
|
||||
{ "100k", 100000 },
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Analog TV RX"; };
|
||||
std::string title() const override { return "AnalogTV RX"; };
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
@@ -41,6 +41,7 @@ std::string type(Packet::Type value) {
|
||||
case Packet::Type::Unknown: return "???";
|
||||
case Packet::Type::IDM: return "IDM";
|
||||
case Packet::Type::SCM: return "SCM";
|
||||
case Packet::Type::SCMPLUS: return "SCM+";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +63,8 @@ std::string commodity_type(CommodityType value) {
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors);
|
||||
std::string entry = ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
|
||||
const ERTRecentEntry::Key ERTRecentEntry::invalid_key { };
|
||||
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "ERT Meter RX"; };
|
||||
std::string title() const override { return "ERT RX"; };
|
||||
|
||||
private:
|
||||
ERTRecentEntries recent { };
|
||||
|
||||
@@ -161,10 +161,16 @@ void ReplayAppView::start() {
|
||||
sample_rate * 8,
|
||||
baseband_bandwidth,
|
||||
rf::Direction::Transmit,
|
||||
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
|
||||
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
|
||||
static_cast<int8_t>(receiver_model.lna()),
|
||||
static_cast<int8_t>(receiver_model.vga())
|
||||
});
|
||||
|
||||
if (portapack::persistent_memory::stealth_mode()){
|
||||
DisplaySleepMessage message;
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ReplayAppView::stop(const bool do_loop) {
|
||||
|
||||
@@ -206,7 +206,7 @@ void SoundBoardView::refresh_list() {
|
||||
file_list[n].string().substr(0, 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
on_select_entry();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Soundboard TX"; };
|
||||
std::string title() const override { return "Soundbrd TX"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace tpms {
|
||||
namespace format {
|
||||
|
||||
static bool use_kpa = true;
|
||||
static bool use_celsius = true;
|
||||
|
||||
std::string type(Reading::Type type) {
|
||||
return to_string_dec_uint(toUType(type), 2);
|
||||
@@ -45,15 +46,11 @@ std::string id(TransponderID id) {
|
||||
}
|
||||
|
||||
std::string pressure(Pressure pressure) {
|
||||
if(use_kpa){
|
||||
return to_string_dec_int(pressure.kilopascal(), 3);
|
||||
}
|
||||
return to_string_dec_int(pressure.psi(), 3);
|
||||
|
||||
return to_string_dec_int(use_kpa? pressure.kilopascal():pressure.psi(), 3);
|
||||
}
|
||||
|
||||
std::string temperature(Temperature temperature) {
|
||||
return to_string_dec_int(temperature.celsius(), 3);
|
||||
return to_string_dec_int(use_celsius? temperature.celsius():temperature.fahrenheit(), 3);
|
||||
}
|
||||
|
||||
std::string flags(Flags flags) {
|
||||
@@ -111,15 +108,15 @@ void RecentEntriesTable<TPMSRecentEntries>::draw(
|
||||
std::string line = tpms::format::type(entry.type) + " " + tpms::format::id(entry.id);
|
||||
|
||||
if( entry.last_pressure.is_valid() ) {
|
||||
line += " " + tpms::format::pressure(entry.last_pressure.value());
|
||||
line += " " + tpms::format::pressure(entry.last_pressure.value());
|
||||
} else {
|
||||
line += " " " ";
|
||||
line += " " " ";
|
||||
}
|
||||
|
||||
if( entry.last_temperature.is_valid() ) {
|
||||
line += " " + tpms::format::temperature(entry.last_temperature.value());
|
||||
line += " " + tpms::format::temperature(entry.last_temperature.value());
|
||||
} else {
|
||||
line += " " " ";
|
||||
line += " " " ";
|
||||
}
|
||||
|
||||
if( entry.received_count > 999 ) {
|
||||
@@ -145,10 +142,12 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
&rssi,
|
||||
&channel,
|
||||
&options_band,
|
||||
&options_pressure,
|
||||
&options_temperature,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&options_type,
|
||||
&recent_entries_view
|
||||
});
|
||||
|
||||
// load app settings
|
||||
@@ -181,16 +180,19 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
};
|
||||
options_band.set_by_value(target_frequency());
|
||||
|
||||
options_type.on_change = [this](size_t, int32_t i) {
|
||||
if (i == 0){
|
||||
tpms::format::use_kpa = true;
|
||||
} else if (i == 1){
|
||||
tpms::format::use_kpa = false;
|
||||
}
|
||||
update_type();
|
||||
options_pressure.on_change = [this](size_t, int32_t i) {
|
||||
tpms::format::use_kpa = !i;
|
||||
update_view();
|
||||
};
|
||||
|
||||
options_type.set_selected_index(0, true);
|
||||
options_pressure.set_selected_index(0, true);
|
||||
|
||||
options_temperature.on_change = [this](size_t, int32_t i) {
|
||||
tpms::format::use_celsius = !i;
|
||||
update_view();
|
||||
};
|
||||
|
||||
options_temperature.set_selected_index(0, true);
|
||||
|
||||
logger = std::make_unique<TPMSLogger>();
|
||||
if( logger ) {
|
||||
@@ -214,16 +216,8 @@ void TPMSAppView::focus() {
|
||||
options_band.focus();
|
||||
}
|
||||
|
||||
void TPMSAppView::update_type() {
|
||||
if (tpms::format::use_kpa){
|
||||
remove_child(&recent_entries_view_psi);
|
||||
add_child(&recent_entries_view_kpa);
|
||||
recent_entries_view_kpa.set_parent_rect(view_normal_rect);
|
||||
} else {
|
||||
remove_child(&recent_entries_view_kpa);
|
||||
add_child(&recent_entries_view_psi);
|
||||
recent_entries_view_psi.set_parent_rect(view_normal_rect);
|
||||
}
|
||||
void TPMSAppView::update_view() {
|
||||
recent_entries_view.set_parent_rect(view_normal_rect);
|
||||
}
|
||||
|
||||
void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
@@ -231,7 +225,7 @@ void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
view_normal_rect = { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
|
||||
|
||||
update_type();
|
||||
update_view();
|
||||
}
|
||||
|
||||
void TPMSAppView::on_packet(const tpms::Packet& packet) {
|
||||
@@ -244,23 +238,13 @@ void TPMSAppView::on_packet(const tpms::Packet& packet) {
|
||||
const auto reading = reading_opt.value();
|
||||
auto& entry = ::on_packet(recent, TPMSRecentEntry::Key { reading.type(), reading.id() });
|
||||
entry.update(reading);
|
||||
|
||||
if(tpms::format::use_kpa){
|
||||
recent_entries_view_kpa.set_dirty();
|
||||
} else {
|
||||
recent_entries_view_psi.set_dirty();
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TPMSAppView::on_show_list() {
|
||||
if(tpms::format::use_kpa){
|
||||
recent_entries_view_kpa.hidden(false);
|
||||
recent_entries_view_kpa.focus();
|
||||
} else {
|
||||
recent_entries_view_psi.hidden(false);
|
||||
recent_entries_view_psi.focus();
|
||||
}
|
||||
recent_entries_view.hidden(false);
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
|
||||
|
||||
@@ -102,8 +102,8 @@ public:
|
||||
void paint(Painter&) override { };
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "TPMS Cars RX"; };
|
||||
|
||||
std::string title() const override { return "TPMS RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 315000000;
|
||||
@@ -144,7 +144,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField options_type {
|
||||
OptionsField options_pressure {
|
||||
{ 5 * 8, 0 * 16 },
|
||||
3,
|
||||
{
|
||||
@@ -153,6 +153,15 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField options_temperature {
|
||||
{ 9 * 8, 0 * 16 },
|
||||
1,
|
||||
{
|
||||
{ "C", 0 },
|
||||
{ "F", 1 }
|
||||
}
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
};
|
||||
@@ -168,31 +177,21 @@ private:
|
||||
TPMSRecentEntries recent { };
|
||||
std::unique_ptr<TPMSLogger> logger { };
|
||||
|
||||
const RecentEntriesColumns columns_kpa { {
|
||||
const RecentEntriesColumns columns { {
|
||||
{ "Tp", 2 },
|
||||
{ "ID", 8 },
|
||||
{ "kPa", 3 },
|
||||
{ "C", 3 },
|
||||
{ "Pres", 4 },
|
||||
{ "Temp", 4 },
|
||||
{ "Cnt", 3 },
|
||||
{ "Fl", 2 },
|
||||
} };
|
||||
TPMSRecentEntriesView recent_entries_view_kpa { columns_kpa, recent };
|
||||
|
||||
const RecentEntriesColumns columns_psi { {
|
||||
{ "Tp", 2 },
|
||||
{ "ID", 8 },
|
||||
{ "PSI", 3 },
|
||||
{ "C", 3 },
|
||||
{ "Cnt", 3 },
|
||||
{ "Fl", 2 },
|
||||
} };
|
||||
TPMSRecentEntriesView recent_entries_view_psi { columns_psi, recent };
|
||||
TPMSRecentEntriesView recent_entries_view { columns, recent };
|
||||
|
||||
uint32_t target_frequency_ = initial_target_frequency;
|
||||
|
||||
void on_packet(const tpms::Packet& packet);
|
||||
void on_show_list();
|
||||
void update_type();
|
||||
void update_view();
|
||||
|
||||
void on_band_changed(const uint32_t new_band_frequency);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "AFSK RX (beta)"; };
|
||||
std::string title() const override { return "AFSK RX"; };
|
||||
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
|
||||
@@ -179,7 +179,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "BHT Xy/EP TX"; };
|
||||
std::string title() const override { return "BHT TX"; };
|
||||
|
||||
private:
|
||||
// app save settings
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "BurgerPgr TX"; };
|
||||
std::string title() const override { return "BurgerPgrTX"; };
|
||||
|
||||
private:
|
||||
enum tx_modes {
|
||||
|
||||
@@ -266,7 +266,9 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
const std::array<Rect, 7> button_rects { {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
const std::array<Rect, 8> button_rects { {
|
||||
{ 64, 32, 16, 16 }, // Right
|
||||
{ 0, 32, 16, 16 }, // Left
|
||||
{ 32, 64, 16, 16 }, // Down
|
||||
@@ -274,27 +276,63 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
{ 32, 32, 16, 16 }, // Select
|
||||
{ 16, 96, 16, 16 }, // Encoder phase 0
|
||||
{ 48, 96, 16, 16 }, // Encoder phase 1
|
||||
{ 96, 0, 16, 16 }, // Dfu
|
||||
} };
|
||||
const auto pos = screen_pos();
|
||||
auto switches_raw = control::debug::switches();
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
auto switches_event = key_event_mask;
|
||||
|
||||
for(const auto r : button_rects) {
|
||||
const auto c =
|
||||
((switches_event & 1) ?
|
||||
Color::red() :
|
||||
((switches_debounced & 1) ?
|
||||
Color::green() :
|
||||
((switches_raw & 1) ?
|
||||
Color::yellow() :
|
||||
Color::blue()
|
||||
)
|
||||
)
|
||||
);
|
||||
painter.fill_rectangle(r + pos, c);
|
||||
painter.fill_rectangle(r + pos, Color::blue());
|
||||
}
|
||||
|
||||
const std::array<Rect, 8> raw_rects { {
|
||||
{ 64 + 1, 32 + 1, 16 - 2, 16 - 2 }, // Right
|
||||
{ 0 + 1, 32 + 1, 16 - 2, 16 - 2 }, // Left
|
||||
{ 32 + 1, 64 + 1, 16 - 2, 16 - 2 }, // Down
|
||||
{ 32 + 1, 0 + 1, 16 - 2, 16 - 2 }, // Up
|
||||
{ 32 + 1, 32 + 1, 16 - 2, 16 - 2 }, // Select
|
||||
{ 16 + 1, 96 + 1, 16 - 2, 16 - 2 }, // Encoder phase 0
|
||||
{ 48 + 1, 96 + 1, 16 - 2, 16 - 2 }, // Encoder phase 1
|
||||
{ 96 + 1, 0 + 1, 16 - 2, 16 - 2 }, // Dfu
|
||||
} };
|
||||
|
||||
auto switches_raw = control::debug::switches();
|
||||
for(const auto r : raw_rects) {
|
||||
if (switches_raw & 1)
|
||||
painter.fill_rectangle(r + pos, Color::yellow());
|
||||
|
||||
switches_raw >>= 1;
|
||||
}
|
||||
|
||||
const std::array<Rect, 6> debounced_rects { {
|
||||
{ 64 + 2, 32 + 2, 16 - 4, 16 - 4 }, // Right
|
||||
{ 0 + 2, 32 + 2, 16 - 4, 16 - 4 }, // Left
|
||||
{ 32 + 2, 64 + 2, 16 - 4, 16 - 4 }, // Down
|
||||
{ 32 + 2, 0 + 2, 16 - 4, 16 - 4 }, // Up
|
||||
{ 32 + 2, 32 + 2, 16 - 4, 16 - 4 }, // Select
|
||||
{ 96 + 2, 0 + 2, 16 - 4, 16 - 4 }, // Dfu
|
||||
} };
|
||||
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
for(const auto r : debounced_rects) {
|
||||
if (switches_debounced & 1)
|
||||
painter.fill_rectangle(r + pos, Color::green());
|
||||
|
||||
switches_debounced >>= 1;
|
||||
}
|
||||
|
||||
const std::array<Rect, 6> events_rects { {
|
||||
{ 64 + 3, 32 + 3, 16 - 6, 16 - 6 }, // Right
|
||||
{ 0 + 3, 32 + 3, 16 - 6, 16 - 6 }, // Left
|
||||
{ 32 + 3, 64 + 3, 16 - 6, 16 - 6 }, // Down
|
||||
{ 32 + 3, 0 + 3, 16 - 6, 16 - 6 }, // Up
|
||||
{ 32 + 3, 32 + 3, 16 - 6, 16 - 6 }, // Select
|
||||
{ 96 + 3, 0 + 3, 16 - 6, 16 - 6 }, // Dfu
|
||||
} };
|
||||
|
||||
auto switches_event = key_event_mask;
|
||||
for(const auto r : events_rects) {
|
||||
if (switches_event & 1)
|
||||
painter.fill_rectangle(r + pos, Color::red());
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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_dfu_menu.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
DfuMenu::DfuMenu(NavigationView& nav) : nav_ (nav) {
|
||||
add_children({
|
||||
&text_head,
|
||||
&labels,
|
||||
&text_info_line_1,
|
||||
&text_info_line_2,
|
||||
&text_info_line_3,
|
||||
&text_info_line_4,
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8
|
||||
});
|
||||
}
|
||||
|
||||
void DfuMenu::paint(Painter& painter) {
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
|
||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||
text_info_line_2.set(to_string_dec_uint((uint32_t)get_free_stack_space(), 6));
|
||||
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
|
||||
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_8.set(to_string_dec_uint(chTimeNow()/1000, 6));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{15 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::black()
|
||||
);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{21 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||
{17 * CHARACTER_WIDTH + margin * 2, 8}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{5 * CHARACTER_WIDTH - margin, (lines+3) * LINE_HEIGHT + margin},
|
||||
{17 * CHARACTER_WIDTH + margin * 2, 8}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __UI_DFU_MENU_H__
|
||||
#define __UI_DFU_MENU_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "debug.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#define LINE_HEIGHT 16
|
||||
#define CHARACTER_WIDTH 8
|
||||
|
||||
namespace ui {
|
||||
class NavigationView;
|
||||
|
||||
class DfuMenu : public View {
|
||||
public:
|
||||
DfuMenu(NavigationView& nav);
|
||||
~DfuMenu() = default;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_head {{ 6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 11 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, "Performance"};
|
||||
|
||||
Labels labels {
|
||||
{ { 6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT }, "M0 heap:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT }, "M0 stack:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT }, "M0 cpu %:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT }, "M4 heap:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT }, "M4 stack:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,10 * LINE_HEIGHT }, "M4 cpu %:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,11 * LINE_HEIGHT }, "M4 miss:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,12 * LINE_HEIGHT }, "uptime:", Color::dark_cyan() }
|
||||
};
|
||||
|
||||
Text text_info_line_1 {{ 15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_2 {{ 15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_3 {{ 15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_4 {{ 15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_5 {{ 15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_6 {{ 15 * CHARACTER_WIDTH,10 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_7 {{ 15 * CHARACTER_WIDTH,11 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_8 {{ 15 * CHARACTER_WIDTH,12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_DFU_MENU_H__*/
|
||||
@@ -20,72 +20,173 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* TODO:
|
||||
* - Paging menu items
|
||||
* - Copy/Move
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
bool is_hidden_file(const fs::path& path) {
|
||||
return !path.empty() && path.native()[0] == u'.';
|
||||
}
|
||||
|
||||
// Gets a truncated name from a path for display.
|
||||
std::string truncate(const fs::path& path, size_t max_length) {
|
||||
auto name = path.string();
|
||||
return name.length() <= max_length ? name : name.substr(0, max_length);
|
||||
}
|
||||
|
||||
// Gets a human readable file size string.
|
||||
std::string get_pretty_size(uint32_t file_size) {
|
||||
static const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
return to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs
|
||||
) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
|
||||
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
||||
if (lhs.is_directory && !rhs.is_directory)
|
||||
return true;
|
||||
else if (!lhs.is_directory && rhs.is_directory)
|
||||
return false;
|
||||
else
|
||||
return lhs.path < rhs.path;
|
||||
});
|
||||
|
||||
entries.insert(it, std::move(entry));
|
||||
}
|
||||
|
||||
// Returns the partner file path or an empty path if no partner is found.
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return { };
|
||||
|
||||
const fs::path txt_path{ u".TXT" };
|
||||
const fs::path c16_path{ u".C16" };
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
else
|
||||
return { };
|
||||
|
||||
path.replace_extension(ext);
|
||||
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{ };
|
||||
}
|
||||
|
||||
// Modal prompt to update the partner file if it exists.
|
||||
// Runs continuation on_partner_action to update the partner file.
|
||||
// Returns true is a partner is found, otherwise false.
|
||||
// Path must be the full path to the file.
|
||||
bool partner_file_prompt(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
std::string action_name,
|
||||
std::function<void(const fs::path&, bool)> on_partner_action
|
||||
) {
|
||||
auto partner = get_partner_file(path);
|
||||
|
||||
if (partner.empty())
|
||||
return false;
|
||||
|
||||
nav.push_under_current<ModalMessageView>(
|
||||
"Partner File",
|
||||
partner.filename().string() + "\n" + action_name + " this file too?",
|
||||
YESNO,
|
||||
[&nav, partner, on_partner_action](bool choice) {
|
||||
if (on_partner_action)
|
||||
on_partner_action(partner, choice);
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
|
||||
/* FileManBaseView ***********************************************************/
|
||||
|
||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
current_path = dir_path;
|
||||
|
||||
text_current.set(dir_path.string().length()? dir_path.string().substr(0, 30 - 6):"(sd root)");
|
||||
|
||||
entry_list.clear();
|
||||
|
||||
auto filtering = (bool)extension_filter.size();
|
||||
|
||||
// List directories and files, put directories up top
|
||||
if (dir_path.string().length())
|
||||
entry_list.push_back({ u"..", 0, true });
|
||||
auto filtering = !extension_filter.empty();
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) {
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (is_hidden_file(entry.path()))
|
||||
continue;
|
||||
|
||||
// do not display dir / files starting with '.' (hidden / tmp)
|
||||
if (entry.path().string().length() && entry.path().filename().string()[0] != '.') {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
bool matched = true;
|
||||
if (filtering) {
|
||||
auto entry_extension = entry.path().extension().string();
|
||||
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
if (entry_extension != extension_filter)
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (std::filesystem::is_directory(entry.status())) {
|
||||
entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
|
||||
}
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, { entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, { entry.path(), 0, true });
|
||||
}
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty())
|
||||
entry_list.insert(entry_list.begin(), { parent_dir_path, 0, true });
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_selected_path() {
|
||||
auto selected_path_str = current_path.string();
|
||||
auto entry_path = entry_list[menu_view.highlighted_index()].entry_path.string();
|
||||
|
||||
if (entry_path == "..") {
|
||||
selected_path_str = get_parent_dir().string();
|
||||
} else {
|
||||
if (selected_path_str.back() != '/')
|
||||
selected_path_str += '/';
|
||||
|
||||
selected_path_str += entry_path;
|
||||
}
|
||||
|
||||
return selected_path_str;
|
||||
fs::path FileManBaseView::get_selected_full_path() const {
|
||||
if (get_selected_entry().path == parent_dir_path)
|
||||
return current_path.parent_path();
|
||||
|
||||
return current_path / get_selected_entry().path;
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_parent_dir() {
|
||||
auto current_path_str = current_path.string();
|
||||
return current_path.string().substr(0, current_path_str.find_last_of('/'));
|
||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||
// TODO: return reference to an "empty" entry on OOB?
|
||||
return entry_list[menu_view.highlighted_index()];
|
||||
}
|
||||
|
||||
FileManBaseView::FileManBaseView(
|
||||
@@ -105,20 +206,20 @@ FileManBaseView::FileManBaseView(
|
||||
};
|
||||
|
||||
if (!sdcIsCardInserted(&SDCD1)) {
|
||||
empty_root=true;
|
||||
empty_root = true;
|
||||
text_current.set("NO SD CARD!");
|
||||
return;
|
||||
}
|
||||
|
||||
load_directory_contents(current_path);
|
||||
|
||||
if (!entry_list.size()) {
|
||||
empty_root = true;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
load_directory_contents(current_path);
|
||||
if (!entry_list.size())
|
||||
{
|
||||
empty_root = true;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
menu_view.on_left = [&nav, this]() {
|
||||
load_directory_contents(get_parent_dir());
|
||||
refresh_list();
|
||||
};
|
||||
}
|
||||
menu_view.on_left = [this]() {
|
||||
pop_dir();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,67 +231,83 @@ void FileManBaseView::focus() {
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
return;
|
||||
|
||||
current_path = current_path.parent_path();
|
||||
reload_current();
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
}
|
||||
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
auto prev_highlight = menu_view.highlighted_index();
|
||||
menu_view.clear();
|
||||
|
||||
for (size_t n = 0; n < entry_list.size(); n++) {
|
||||
auto entry = &entry_list[n];
|
||||
auto entry_name = entry->entry_path.filename().string().substr(0, 20);
|
||||
|
||||
if (entry->is_directory) {
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
|
||||
if (entry.is_directory) {
|
||||
menu_view.add_item({
|
||||
entry_name,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](){
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
auto file_size = entry->size;
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
std::string size_str = to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
|
||||
auto entry_extension = entry->entry_path.extension().string();
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
// Associate extension to icon and color
|
||||
size_t c;
|
||||
for (c = 0; c < file_types.size() - 1; c++) {
|
||||
if (entry_extension == file_types[c].extension)
|
||||
break;
|
||||
}
|
||||
const auto& assoc = get_assoc(entry.path.extension());
|
||||
auto size_str = get_pretty_size(entry.size);
|
||||
|
||||
menu_view.add_item({
|
||||
entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
file_types[c].color,
|
||||
file_types[c].icon,
|
||||
[this](){
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(0); // Refresh
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current() {
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const
|
||||
{
|
||||
size_t index = 0;
|
||||
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
return file_types[index];
|
||||
}
|
||||
|
||||
/*void FileSaveView::on_save_name() {
|
||||
@@ -215,8 +332,9 @@ FileSaveView::FileSaveView(
|
||||
};
|
||||
}*/
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool v) {
|
||||
(void)v; //avoid unused warning
|
||||
/* FileLoadView **************************************************************/
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -238,45 +356,92 @@ FileLoadView::FileLoadView(
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [&nav, this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
on_select_entry = [this](KeyEvent) {
|
||||
if (get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
nav_.pop();
|
||||
if (on_changed)
|
||||
on_changed(current_path.string() + '/' + entry_list[menu_view.highlighted_index()].entry_path.string());
|
||||
on_changed(get_selected_full_path());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void FileManagerView::on_rename(NavigationView& nav) {
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
std::string destination_path = current_path.string();
|
||||
if (destination_path.back() != '/')
|
||||
destination_path += '/';
|
||||
destination_path = destination_path + buffer;
|
||||
rename_file(get_selected_path(), destination_path);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::on_rename() {
|
||||
auto& entry = get_selected_entry();
|
||||
name_buffer = entry.path.filename().string();
|
||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||
|
||||
if (auto pos = name_buffer.find_last_of(".");
|
||||
pos != name_buffer.npos && !entry.is_directory)
|
||||
cursor_pos = pos;
|
||||
|
||||
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
|
||||
[this](std::string& renamed) {
|
||||
auto renamed_path = fs::path{ renamed };
|
||||
rename_file(get_selected_full_path(), current_path / renamed_path);
|
||||
|
||||
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
|
||||
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
|
||||
if (should_rename) {
|
||||
auto new_name = renamed_path.replace_extension(partner.extension());
|
||||
rename_file(current_path / partner, current_path / new_name);
|
||||
}
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
void FileManagerView::on_delete() {
|
||||
delete_file(get_selected_path());
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
auto name = get_selected_entry().path.filename().string();
|
||||
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(get_selected_full_path());
|
||||
|
||||
auto has_partner = partner_file_prompt(
|
||||
nav_, get_selected_full_path(), "Delete",
|
||||
[this](const fs::path& partner, bool should_delete) {
|
||||
if (should_delete)
|
||||
delete_file(current_path / partner);
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
bool FileManagerView::selected_is_valid() const {
|
||||
return !entry_list.empty() &&
|
||||
get_selected_entry().path != parent_dir_path;
|
||||
}
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
button_rename.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
button_delete.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
FileManagerView::~FileManagerView() {
|
||||
// Flush ?
|
||||
}
|
||||
|
||||
FileManagerView::FileManagerView(
|
||||
@@ -293,47 +458,40 @@ FileManagerView::FileManagerView(
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_delete,
|
||||
&button_new_dir,
|
||||
&button_delete
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_path())));
|
||||
// TODO: enable/disable buttons.
|
||||
if (selected_is_valid())
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
} else
|
||||
|
||||
on_select_entry = [this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
button_rename.focus();
|
||||
}
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this, &nav](Button&) {
|
||||
name_buffer.clear();
|
||||
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
make_new_directory(current_path.string() + '/' + buffer);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
button_rename.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_rename();
|
||||
};
|
||||
|
||||
button_rename.on_select = [this, &nav](Button&) {
|
||||
name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
|
||||
on_rename(nav);
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav](Button&) {
|
||||
// Use display_modal ?
|
||||
nav.push<ModalMessageView>("Delete", "Delete " + entry_list[menu_view.highlighted_index()].entry_path.filename().string() + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
on_delete();
|
||||
}
|
||||
);
|
||||
|
||||
button_new_dir.on_select = [this](Button&) {
|
||||
on_new_dir();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
namespace ui {
|
||||
|
||||
struct fileman_entry {
|
||||
std::filesystem::path entry_path { };
|
||||
std::filesystem::path path { };
|
||||
uint32_t size { };
|
||||
bool is_directory { };
|
||||
};
|
||||
@@ -43,50 +43,56 @@ public:
|
||||
std::string filter
|
||||
);
|
||||
|
||||
void focus() override;
|
||||
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
std::filesystem::path get_selected_path();
|
||||
|
||||
std::string title() const override { return "File manager"; };
|
||||
void focus() override;
|
||||
std::string title() const override { return "Fileman"; };
|
||||
|
||||
protected:
|
||||
NavigationView& nav_;
|
||||
|
||||
static constexpr size_t max_filename_length = 30 - 2;
|
||||
|
||||
const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
|
||||
static constexpr size_t max_filename_length = 50;
|
||||
|
||||
struct file_assoc_t {
|
||||
std::string extension;
|
||||
std::filesystem::path extension;
|
||||
const Bitmap* icon;
|
||||
ui::Color color;
|
||||
};
|
||||
|
||||
const std::vector<file_assoc_t> file_types = {
|
||||
{ ".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ ".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".BMP", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".C8", &bitmap_icon_file_iq, ui::Color::blue() },
|
||||
{ ".C16", &bitmap_icon_file_iq, ui::Color::blue() },
|
||||
{ ".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ "", &bitmap_icon_file, ui::Color::light_grey() }
|
||||
{ u".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ u".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ u".BMP", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ u"", &bitmap_icon_file, ui::Color::light_grey() } // NB: Must be last.
|
||||
};
|
||||
|
||||
bool empty_root { false };
|
||||
std::function<void(void)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::string extension_filter { "" };
|
||||
|
||||
void change_category(int32_t category_id);
|
||||
std::filesystem::path get_parent_dir();
|
||||
|
||||
|
||||
std::filesystem::path get_selected_full_path() const;
|
||||
const fileman_entry& get_selected_entry() const;
|
||||
|
||||
void push_dir(const std::filesystem::path& path);
|
||||
void pop_dir();
|
||||
void refresh_list();
|
||||
void reload_current();
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
bool empty_root { false };
|
||||
std::function<void(KeyEvent)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
|
||||
const std::filesystem::path parent_dir_path { u".." };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::filesystem::path extension_filter { u"" };
|
||||
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::vector<uint32_t> saved_index_stack { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 0 }, "Path:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_current {
|
||||
{ 6 * 8, 0 * 8, 24 * 8, 16 },
|
||||
"",
|
||||
@@ -142,11 +148,16 @@ public:
|
||||
~FileManagerView();
|
||||
|
||||
private:
|
||||
// Passed by ref to other views needing lifetime extension.
|
||||
std::string name_buffer { };
|
||||
|
||||
void refresh_widgets(const bool v);
|
||||
void on_rename(NavigationView& nav);
|
||||
void on_rename();
|
||||
void on_delete();
|
||||
void on_new_dir();
|
||||
|
||||
// True if the selected entry is a real file item.
|
||||
bool selected_is_valid() const;
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
|
||||
@@ -161,16 +172,16 @@ private:
|
||||
{ 0 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Rename"
|
||||
};
|
||||
|
||||
Button button_delete {
|
||||
{ 16 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Delete"
|
||||
};
|
||||
|
||||
|
||||
Button button_new_dir {
|
||||
{ 0 * 8, 34 * 8, 14 * 8, 32 },
|
||||
"New dir"
|
||||
"New Dir"
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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_flash_utility.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
Thread* FlashUtilityView::thread { nullptr };
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
FlashUtilityView::FlashUtilityView(NavigationView& nav) : nav_ (nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&menu_view
|
||||
});
|
||||
|
||||
menu_view.set_parent_rect({ 0, 3 * 8, 240, 33 * 8 });
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
menu_view.add_item({
|
||||
filename.string().substr(0, max_filename_length),
|
||||
ui::Color::red(),
|
||||
&bitmap_icon_temperature,
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
|
||||
nav_.push<ModalMessageView>(
|
||||
"Warning!",
|
||||
"This will replace your\ncurrent firmware.\n\nIf things go wrong you are\nrequired to flash manually\nwith dfu.",
|
||||
YESNO,
|
||||
[this, path](bool choice) {
|
||||
if (choice) {
|
||||
std::u16string full_path = std::u16string(u"FIRMWARE/") + path;
|
||||
this->flash_firmware(full_path);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{ 0, 0, portapack::display.width(), portapack::display.height() },
|
||||
ui::Color::black()
|
||||
);
|
||||
|
||||
painter.draw_string({ 12, 24 }, this->nav_.style(), "This will take 15 seconds.");
|
||||
painter.draw_string({ 12, 64 }, this->nav_.style(), "Please wait while LEDs RX");
|
||||
painter.draw_string({ 12, 84 }, this->nav_.style(), "and TX are flashing.");
|
||||
painter.draw_string({ 12, 124 }, this->nav_.style(), "Then restart the device.");
|
||||
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
}
|
||||
|
||||
void FlashUtilityView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __UI_FLSH_UITILTY_H__
|
||||
#define __UI_FLSH_UITILTY_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ff.h"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class FlashUtilityView : public View {
|
||||
public:
|
||||
FlashUtilityView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
bool confirmed = false;
|
||||
static Thread* thread;
|
||||
|
||||
Labels labels {
|
||||
{ { 4, 4 }, "Select firmware to flash:", Color::white() }
|
||||
};
|
||||
|
||||
MenuView menu_view {
|
||||
{ 0, 2 * 8, 240, 26 * 8 },
|
||||
true
|
||||
};
|
||||
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_FLSH_UITILTY_H__*/
|
||||
@@ -120,7 +120,7 @@ void FreqManBaseView::refresh_list() {
|
||||
freqman_item_string(database[n], 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
if (on_select_frequency)
|
||||
on_select_frequency();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class FrequencySaveView : public FreqManBaseView {
|
||||
public:
|
||||
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
|
||||
|
||||
std::string title() const override { return "Save frequency"; };
|
||||
std::string title() const override { return "Save freq."; };
|
||||
|
||||
private:
|
||||
std::string desc_buffer { };
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
|
||||
FrequencyLoadView(NavigationView& nav);
|
||||
|
||||
std::string title() const override { return "Load frequency"; };
|
||||
std::string title() const override { return "Load freq."; };
|
||||
|
||||
private:
|
||||
void refresh_widgets(const bool v);
|
||||
@@ -137,7 +137,7 @@ public:
|
||||
FrequencyManagerView(NavigationView& nav);
|
||||
~FrequencyManagerView();
|
||||
|
||||
std::string title() const override { return "Freq. manager"; };
|
||||
std::string title() const override { return "Freqman"; };
|
||||
|
||||
private:
|
||||
std::string desc_buffer { };
|
||||
|
||||
@@ -180,7 +180,8 @@ namespace ui {
|
||||
};
|
||||
|
||||
// default peak value
|
||||
peak_mode.set_selected_index(1);
|
||||
peak_mode.set_selected_index(2);
|
||||
rssi_resolution.set_selected_index(1);
|
||||
//FILL STEP OPTIONS
|
||||
freqman_set_modulation_option( field_mode );
|
||||
freqman_set_step_option_short( step_mode );
|
||||
|
||||
@@ -41,200 +41,195 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
class LevelView : public View {
|
||||
public:
|
||||
LevelView(NavigationView& nav);
|
||||
~LevelView();
|
||||
class LevelView : public View {
|
||||
public:
|
||||
LevelView(NavigationView& nav);
|
||||
~LevelView();
|
||||
|
||||
void focus() override;
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq( int64_t f );
|
||||
const Style style_grey { // level
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
|
||||
const Style style_grey { // level
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
const Style style_white { // level
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style style_white { // level
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
const Style style_yellow { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
|
||||
const Style style_yellow { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
const Style style_blue { // quick level, wait == 0
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
};
|
||||
|
||||
const Style style_blue { // quick level, wait == 0
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
};
|
||||
std::string title() const override { return "Level"; };
|
||||
|
||||
std::string title() const override { return "Level"; };
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
size_t change_mode( freqman_index_t mod_type);
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void set_display_freq( int64_t freq );
|
||||
bool check_sd_card();
|
||||
|
||||
size_t change_mode( freqman_index_t mod_type);
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void set_display_freq( int64_t freq );
|
||||
bool check_sd_card();
|
||||
int32_t db { 0 };
|
||||
long long int MAX_UFREQ = { 7200000000 }; // maximum usable freq
|
||||
bool sd_card_mounted = false ;
|
||||
rf::Frequency freq = { 0 } ;
|
||||
|
||||
int32_t db { 0 };
|
||||
long long int MAX_UFREQ = { 7200000000 }; // maximum usable freq
|
||||
bool sd_card_mounted = false ;
|
||||
rf::Frequency freq = { 0 } ;
|
||||
Labels labels
|
||||
{
|
||||
{ { 0 * 8 , 0 * 16 }, "LNA: VGA: AMP: VOL: ", Color::light_grey() },
|
||||
{ { 0 * 8 , 1 * 16 }, "BW: MODE: S: ", Color::light_grey() },
|
||||
};
|
||||
|
||||
Labels labels
|
||||
{
|
||||
{ { 0 * 8 , 0 * 16 }, "LNA: VGA: AMP: VOL: ", Color::light_grey() },
|
||||
{ { 0 * 8 , 1 * 16 }, "BW: MODE: S: ", Color::light_grey() },
|
||||
};
|
||||
LNAGainField field_lna {
|
||||
{ 4 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
{ 4 * 8, 0 * 16 }
|
||||
};
|
||||
VGAGainField field_vga {
|
||||
{ 11 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 11 * 8, 0 * 16 }
|
||||
};
|
||||
RFAmpField field_rf_amp {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
NumberField field_volume {
|
||||
{ 24 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 24 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
OptionsField field_bw {
|
||||
{ 3 * 8, 1 * 16 },
|
||||
6,
|
||||
{ }
|
||||
};
|
||||
|
||||
OptionsField field_bw {
|
||||
{ 3 * 8, 1 * 16 },
|
||||
6,
|
||||
{ }
|
||||
};
|
||||
OptionsField field_mode {
|
||||
{ 15 * 8, 1 * 16 },
|
||||
3,
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField field_mode {
|
||||
{ 15 * 8, 1 * 16 },
|
||||
3,
|
||||
{
|
||||
}
|
||||
};
|
||||
OptionsField step_mode {
|
||||
{ 16 * 8, 2 * 16 + 4 },
|
||||
12,
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField step_mode {
|
||||
{ 16 * 8, 2 * 16 + 4 },
|
||||
12,
|
||||
{
|
||||
}
|
||||
};
|
||||
ButtonWithEncoder button_frequency {
|
||||
{ 0 * 8 , 2 * 16 + 8 , 15 * 8 , 1 * 8 },
|
||||
""
|
||||
};
|
||||
|
||||
RSSIGraph rssi_graph { // 240x320 =>
|
||||
{ 0 , 5 * 16 + 4 , 240 - 5 * 8 , 216 },
|
||||
};
|
||||
OptionsField audio_mode {
|
||||
{ 21 * 8, 1 * 16 },
|
||||
9,
|
||||
{
|
||||
{"audio off", 0},
|
||||
{"audio on",1}
|
||||
//{"tone on", 2},
|
||||
//{"tone off", 2},
|
||||
}
|
||||
};
|
||||
|
||||
RSSI rssi { // 240x320 =>
|
||||
{ 240 - 5 * 8 , 5 * 16 + 4 , 5 * 8 , 216 },
|
||||
};
|
||||
Text text_ctcss {
|
||||
{ 22 * 8, 3 * 16 + 4 , 14 * 8, 1 * 8 },
|
||||
""
|
||||
};
|
||||
|
||||
// RSSI: XX/XX/XXX,dt: XX
|
||||
Text freq_stats_rssi {
|
||||
{ 0 * 8 , 3 * 16 + 4 , 22 * 8, 14 },
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_frequency {
|
||||
{ 0 * 8 , 2 * 16 + 8 , 15 * 8 , 1 * 8 },
|
||||
""
|
||||
};
|
||||
// Power: -XXX db
|
||||
Text freq_stats_db {
|
||||
{ 0 * 8 , 4 * 16 + 4 , 14 * 8, 14 },
|
||||
};
|
||||
|
||||
OptionsField audio_mode {
|
||||
{ 21 * 8, 1 * 16 },
|
||||
9,
|
||||
{
|
||||
{"audio off", 0},
|
||||
{"audio on",1}
|
||||
//{"tone on", 2},
|
||||
//{"tone off", 2},
|
||||
}
|
||||
};
|
||||
OptionsField peak_mode {
|
||||
{ 40 + 10 * 8, 4 * 16 + 4 },
|
||||
10,
|
||||
{
|
||||
{"peak:none", 0},
|
||||
{"peak:0.25s",250},
|
||||
{"peak:0.5s",500},
|
||||
{"peak:1s",1000},
|
||||
{"peak:3s",3000},
|
||||
{"peak:5s",5000},
|
||||
{"peak:10s",10000},
|
||||
}
|
||||
};
|
||||
OptionsField rssi_resolution {
|
||||
{ 44 + 20 * 8, 4 * 16 + 4},
|
||||
4,
|
||||
{
|
||||
{"16x", 16},
|
||||
{"32x", 32},
|
||||
{"64x", 64},
|
||||
{"128x", 128},
|
||||
{"240x", 240},
|
||||
}
|
||||
};
|
||||
|
||||
RSSIGraph rssi_graph { // 240x320 =>
|
||||
{ 0 , 5 * 16 + 4 , 240 - 5 * 8 , 320 - ( 5 * 16 + 4 ) },
|
||||
};
|
||||
|
||||
Text text_ctcss {
|
||||
{ 22 * 8, 3 * 16 + 4 , 14 * 8, 1 * 8 },
|
||||
""
|
||||
};
|
||||
RSSI rssi { // 240x320 =>
|
||||
{ 240 - 5 * 8 , 5 * 16 + 4 , 5 * 8 , 320 - ( 5 * 16 + 4 ) },
|
||||
};
|
||||
|
||||
// RSSI: XX/XX/XXX,dt: XX
|
||||
Text freq_stats_rssi {
|
||||
{ 0 * 8 , 3 * 16 + 4 , 22 * 8, 16 },
|
||||
};
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
|
||||
Text freq_stats_db {
|
||||
{ 0 * 8 , 4 * 16 + 4 , 15 * 8, 16 },
|
||||
};
|
||||
MessageHandlerRegistration message_handler_coded_squelch {
|
||||
Message::ID::CodedSquelch,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||
this->handle_coded_squelch(message.value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
OptionsField peak_mode {
|
||||
{ 40 + 10 * 8, 4 * 16 + 4 },
|
||||
10,
|
||||
{
|
||||
{"peak:none", 0},
|
||||
{"peak:0.25s",250},
|
||||
{"peak:0.5s",500},
|
||||
{"peak:1s",1000},
|
||||
{"peak:3s",3000},
|
||||
{"peak:5s",5000},
|
||||
{"peak:10s",10000},
|
||||
}
|
||||
};
|
||||
OptionsField rssi_resolution {
|
||||
{ 44 + 20 * 8, 4 * 16 + 4},
|
||||
4,
|
||||
{
|
||||
{"16x", 16},
|
||||
{"32x", 32},
|
||||
{"64x", 64},
|
||||
{"128x", 128},
|
||||
{"240x", 240},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
|
||||
MessageHandlerRegistration message_handler_coded_squelch {
|
||||
Message::ID::CodedSquelch,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||
this->handle_coded_squelch(message.value);
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
// app save settings
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
};
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
// app save settings
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
@@ -26,10 +26,9 @@ using namespace portapack;
|
||||
|
||||
namespace ui
|
||||
{
|
||||
|
||||
void GlassView::focus()
|
||||
{
|
||||
field_marker.focus();
|
||||
button_marker.focus();
|
||||
}
|
||||
|
||||
GlassView::~GlassView()
|
||||
@@ -39,6 +38,24 @@ namespace ui
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
// Returns the next multiple of num that is a multiple of multiplier
|
||||
int64_t GlassView::next_mult_of(int64_t num, int64_t multiplier) {
|
||||
return ((num / multiplier) + 1) * multiplier;
|
||||
}
|
||||
|
||||
void GlassView::adjust_range(int64_t* f_min, int64_t* f_max, int64_t width) {
|
||||
int64_t span = *f_max - *f_min;
|
||||
int64_t num_intervals = span / width;
|
||||
if( span % width != 0 )
|
||||
{
|
||||
num_intervals++;
|
||||
}
|
||||
int64_t new_span = num_intervals * width;
|
||||
int64_t delta_span = (new_span - span) / 2;
|
||||
*f_min -= delta_span;
|
||||
*f_max += delta_span;
|
||||
}
|
||||
|
||||
void GlassView::on_lna_changed(int32_t v_db)
|
||||
{
|
||||
receiver_model.set_lna(v_db);
|
||||
@@ -49,12 +66,71 @@ namespace ui
|
||||
receiver_model.set_vga(v_db);
|
||||
}
|
||||
|
||||
void GlassView::add_spectrum_pixel(Color color)
|
||||
void GlassView::reset_live_view( bool clear_screen )
|
||||
{
|
||||
spectrum_row[pixel_index++] = color;
|
||||
max_freq_hold = 0 ;
|
||||
max_freq_power = -1000 ;
|
||||
if( clear_screen )
|
||||
{
|
||||
// only clear screen in peak mode
|
||||
if( live_frequency_view == 2 )
|
||||
{
|
||||
display.fill_rectangle( { { 0 , 108 + 16 } , { 240 , 320 - (108 + 16) } } , { 0 , 0 , 0 } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GlassView::add_spectrum_pixel( uint8_t power )
|
||||
{
|
||||
static int64_t last_max_freq = 0 ;
|
||||
|
||||
spectrum_row[pixel_index] = spectrum_rgb3_lut[power] ; // row of colors
|
||||
spectrum_data[pixel_index] = ( live_frequency_integrate * spectrum_data[pixel_index] + power ) / (live_frequency_integrate + 1); // smoothing
|
||||
pixel_index ++ ;
|
||||
|
||||
if (pixel_index == 240) // got an entire waterfall line
|
||||
{
|
||||
display.draw_pixels({{0, display.scroll(1)}, {240, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
if( live_frequency_view > 0 )
|
||||
{
|
||||
constexpr int rssi_sample_range = 256;
|
||||
constexpr float rssi_voltage_min = 0.4;
|
||||
constexpr float rssi_voltage_max = 2.2;
|
||||
constexpr float adc_voltage_max = 3.3;
|
||||
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
||||
constexpr int raw_delta = raw_max - raw_min;
|
||||
const range_t<int> y_max_range { 0 , 320 - ( 108 + 16 ) };
|
||||
|
||||
//drawing and keeping track of max freq
|
||||
for( uint16_t xpos = 0 ; xpos < 240 ; xpos ++ )
|
||||
{
|
||||
// save max powerwull freq
|
||||
if( spectrum_data[ xpos ] > max_freq_power )
|
||||
{
|
||||
max_freq_power = spectrum_data[ xpos ];
|
||||
max_freq_hold = f_min + ( (f_max - f_min) * xpos) / 240 ;
|
||||
}
|
||||
|
||||
int16_t point = y_max_range.clip( ( ( spectrum_data[ xpos ] - raw_min ) * ( 320 - ( 108 + 16 ) ) ) / raw_delta );
|
||||
uint8_t color_gradient = (point * 255) / 212 ;
|
||||
// clear if not in peak view
|
||||
if( live_frequency_view != 2 )
|
||||
{
|
||||
display.fill_rectangle( { { xpos , 108 + 16 } , { 1 , 320 - point } } , { 0 , 0 , 0 } );
|
||||
}
|
||||
display.fill_rectangle( { { xpos , 320 - point } , { 1 , point } } , { color_gradient , 0 , uint8_t( 255 - color_gradient ) } );
|
||||
}
|
||||
if( last_max_freq != max_freq_hold )
|
||||
{
|
||||
last_max_freq = max_freq_hold ;
|
||||
freq_stats.set( "MAX HOLD: "+to_string_short_freq( max_freq_hold ) );
|
||||
}
|
||||
PlotMarker( marker );
|
||||
}
|
||||
else
|
||||
{
|
||||
display.draw_pixels({{0, display.scroll(1)}, {240, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
}
|
||||
pixel_index = 0; // Start New cascade line
|
||||
}
|
||||
}
|
||||
@@ -63,53 +139,123 @@ namespace ui
|
||||
// Each having the radio signal power for it's corresponding frequency slot
|
||||
void GlassView::on_channel_spectrum(const ChannelSpectrum &spectrum)
|
||||
{
|
||||
// default fast scan offset
|
||||
uint8_t offset = 2 ;
|
||||
baseband::spectrum_streaming_stop();
|
||||
|
||||
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
|
||||
// Spectrum.db has 256 bins. Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
|
||||
// All things said and done, we actually need 240 of those bins:
|
||||
for (uint8_t bin = 0; bin < 240; bin++)
|
||||
if( fast_scan || ( LOOKING_GLASS_SLICE_WIDTH < LOOKING_GLASS_SLICE_WIDTH_MAX ) )
|
||||
{
|
||||
if (bin < 120)
|
||||
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
|
||||
// Spectrum.db has 256 bins.
|
||||
// All things said and done, we actually need 240 of those bins
|
||||
for (uint8_t bin = 0; bin < 240; bin++)
|
||||
{
|
||||
if (spectrum.db[134 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[134 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[bin - 118] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 118];
|
||||
}
|
||||
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
add_spectrum_pixel(spectrum_rgb3_lut[max_power]); // Pixel will represent max_power
|
||||
else
|
||||
add_spectrum_pixel(0); // Filtered out, show black
|
||||
|
||||
max_power = 0;
|
||||
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
// if the view is done in one pass, show it like in analog_audio_app
|
||||
if( ( LOOKING_GLASS_SLICE_WIDTH < LOOKING_GLASS_SLICE_WIDTH_MAX ) )
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
return;
|
||||
// Center 16 bins are ignored (DC spike is blanked)
|
||||
if (bin < 120)
|
||||
{
|
||||
if (spectrum.db[256 - 120 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[256 - 120 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[ bin - 120] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 120];
|
||||
}
|
||||
}
|
||||
else // view is made in multiple pass, use original bin picking
|
||||
{
|
||||
// Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
|
||||
if (bin < 120)
|
||||
{
|
||||
if (spectrum.db[134 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[134 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[bin - 118] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 118];
|
||||
}
|
||||
}
|
||||
|
||||
if( bin == 120 )
|
||||
{
|
||||
bins_Hz_size += 12 * each_bin_size; // add DC bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
}
|
||||
else
|
||||
{
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
}
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
|
||||
f_center += LOOKING_GLASS_SLICE_WIDTH; // Move into the next bandwidth slice NOTE: spectrum.sampling_rate = LOOKING_GLASS_SLICE_WIDTH
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
add_spectrum_pixel(max_power); // Pixel will represent max_power
|
||||
else
|
||||
add_spectrum_pixel(0); // Filtered out, show black
|
||||
|
||||
max_power = 0;
|
||||
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini - offset * each_bin_size ; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
return;
|
||||
}
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
f_center += ( 256 - ( 2 * offset ) ) * each_bin_size ; // Move into the next bandwidth slice NOTE: spectrum.sampling_rate = LOOKING_GLASS_SLICE_WIDTH
|
||||
// lost bins are taken in account so next slice first ignored bins overlap previous kept ones
|
||||
}
|
||||
else //slow scan
|
||||
{
|
||||
offset = 32 ;
|
||||
uint8_t bin_length = 80 ;
|
||||
for (uint8_t bin = offset ; bin < bin_length + offset ; bin++)
|
||||
{
|
||||
if (bin < 120)
|
||||
{
|
||||
if (spectrum.db[134 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[134 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[bin - 118] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 118];
|
||||
}
|
||||
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
add_spectrum_pixel(max_power); // Pixel will represent max_power
|
||||
else
|
||||
add_spectrum_pixel(0); // Filtered out, show black
|
||||
|
||||
max_power = 0;
|
||||
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini - offset * each_bin_size ; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
return;
|
||||
}
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
f_center += bin_length * each_bin_size ;
|
||||
}
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(5);
|
||||
|
||||
// receiver_model.set_tuning_frequency(f_center); //tune rx for this slice
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
}
|
||||
|
||||
@@ -127,50 +273,71 @@ namespace ui
|
||||
|
||||
void GlassView::on_range_changed()
|
||||
{
|
||||
reset_live_view( false );
|
||||
f_min = field_frequency_min.value();
|
||||
f_max = field_frequency_max.value();
|
||||
search_span = f_max - f_min;
|
||||
|
||||
field_marker.set_range(f_min, f_max); // Move the marker between range
|
||||
field_marker.set_value(f_min + (search_span / 2)); // Put MARKER AT MIDDLE RANGE
|
||||
text_range.set(to_string_dec_uint(search_span));
|
||||
|
||||
if( locked_range )
|
||||
{
|
||||
button_range.set_text(">"+to_string_dec_uint(search_span)+"<");
|
||||
}
|
||||
else
|
||||
{
|
||||
button_range.set_text(" "+to_string_dec_uint(search_span)+" ");
|
||||
}
|
||||
|
||||
f_min = (f_min)*MHZ_DIV; // Transpose into full frequency realm
|
||||
f_max = (f_max)*MHZ_DIV;
|
||||
search_span = search_span * MHZ_DIV;
|
||||
adjust_range( &f_min , &f_max , 240 );
|
||||
|
||||
marker_pixel_step = search_span / 240; // Each pixel value in Hz
|
||||
text_marker_pm.set(to_string_dec_uint((marker_pixel_step / X2_MHZ_DIV) + 1)); // Give idea of +/- marker precision
|
||||
marker_pixel_step = (f_max - f_min) / 240; // Each pixel value in Hz
|
||||
marker = f_min + (f_max - f_min) / 2 ;
|
||||
button_marker.set_text( to_string_short_freq( marker ) );
|
||||
PlotMarker( marker ); // Refresh marker on screen
|
||||
|
||||
int32_t marker_step = marker_pixel_step / MHZ_DIV;
|
||||
if (!marker_step)
|
||||
field_marker.set_step(1); // in case selected range is less than 240 (pixels)
|
||||
else
|
||||
field_marker.set_step(marker_step); // step needs to be a pixel wide.
|
||||
|
||||
f_center_ini = f_min + (LOOKING_GLASS_SLICE_WIDTH / 2); // Initial center frequency for sweep
|
||||
|
||||
PlotMarker(field_marker.value()); // Refresh marker on screen
|
||||
|
||||
f_center = f_center_ini; // Reset sweep into first slice
|
||||
pixel_index = 0; // reset pixel counter
|
||||
max_power = 0;
|
||||
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
|
||||
if( (f_max - f_min) <= LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
{
|
||||
LOOKING_GLASS_SLICE_WIDTH = (f_max - f_min) ;
|
||||
receiver_model.set_sampling_rate(LOOKING_GLASS_SLICE_WIDTH);
|
||||
receiver_model.set_baseband_bandwidth(LOOKING_GLASS_SLICE_WIDTH/2);
|
||||
}
|
||||
else if( LOOKING_GLASS_SLICE_WIDTH != LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
{
|
||||
LOOKING_GLASS_SLICE_WIDTH = LOOKING_GLASS_SLICE_WIDTH_MAX ;
|
||||
receiver_model.set_sampling_rate(LOOKING_GLASS_SLICE_WIDTH);
|
||||
receiver_model.set_baseband_bandwidth(LOOKING_GLASS_SLICE_WIDTH);
|
||||
}
|
||||
if( next_mult_of( LOOKING_GLASS_SLICE_WIDTH , 256 ) > LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
LOOKING_GLASS_SLICE_WIDTH = LOOKING_GLASS_SLICE_WIDTH_MAX ;
|
||||
else
|
||||
LOOKING_GLASS_SLICE_WIDTH = next_mult_of( LOOKING_GLASS_SLICE_WIDTH , 256 );
|
||||
|
||||
receiver_model.set_squelch_level(0);
|
||||
each_bin_size = LOOKING_GLASS_SLICE_WIDTH / 256 ;
|
||||
f_center_ini = f_min + (LOOKING_GLASS_SLICE_WIDTH / 2) ; // Initial center frequency for sweep
|
||||
f_center = f_center_ini ; // Reset sweep into first slice
|
||||
baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, field_trigger.value());
|
||||
receiver_model.set_tuning_frequency(f_center_ini); // tune rx for this slice
|
||||
}
|
||||
|
||||
void GlassView::PlotMarker(rf::Frequency pos)
|
||||
{
|
||||
pos = pos * MHZ_DIV;
|
||||
pos -= f_min;
|
||||
pos = pos / marker_pixel_step; // Real pixel
|
||||
|
||||
portapack::display.fill_rectangle({0, 100, 240, 8}, Color::black()); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({(int)pos - 2, 100, 5, 3}, Color::red()); // Red marker top
|
||||
portapack::display.fill_rectangle({(int)pos - 1, 103, 3, 3}, Color::red()); // Red marker middle
|
||||
portapack::display.fill_rectangle({(int)pos, 106, 1, 2}, Color::red()); // Red marker bottom
|
||||
|
||||
uint8_t shift_y = 0 ;
|
||||
if( live_frequency_view > 0 ) // plot one line down when in live view
|
||||
{
|
||||
shift_y = 16 ;
|
||||
}
|
||||
portapack::display.fill_rectangle({0, 100 + shift_y, 240, 8}, Color::black()); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({(int)pos - 2, 100 + shift_y, 5, 3}, Color::red()); // Red marker top
|
||||
portapack::display.fill_rectangle({(int)pos - 1, 103 + shift_y, 3, 3}, Color::red()); // Red marker middle
|
||||
portapack::display.fill_rectangle({(int)pos, 106 + shift_y, 1, 2}, Color::red()); // Red marker bottom
|
||||
}
|
||||
|
||||
GlassView::GlassView(
|
||||
@@ -183,96 +350,116 @@ namespace ui
|
||||
&field_frequency_max,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&text_range,
|
||||
&button_range,
|
||||
&steps_config,
|
||||
&scan_type,
|
||||
&view_config,
|
||||
&level_integration,
|
||||
&filter_config,
|
||||
&field_rf_amp,
|
||||
&range_presets,
|
||||
&field_marker,
|
||||
&text_marker_pm,
|
||||
&field_trigger});
|
||||
&button_marker,
|
||||
&field_trigger,
|
||||
&button_jump,
|
||||
&button_rst,
|
||||
&freq_stats});
|
||||
|
||||
load_Presets(); // Load available presets from TXT files (or default)
|
||||
|
||||
field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset
|
||||
field_frequency_min.set_step( steps );
|
||||
field_frequency_min.on_change = [this](int32_t v)
|
||||
{
|
||||
int32_t steps_ = steps ;
|
||||
if( steps_ < 20 )
|
||||
steps_ = 20 ;
|
||||
if( v > 7200 - steps_ )
|
||||
reset_live_view( true );
|
||||
int32_t min_size = steps ;
|
||||
if( locked_range )
|
||||
min_size = search_span ;
|
||||
if( min_size < 2 )
|
||||
min_size = 2 ;
|
||||
if( v > 7200 - min_size )
|
||||
{
|
||||
v = 7200 - steps_ ;
|
||||
v = 7200 - min_size ;
|
||||
field_frequency_min.set_value( v );
|
||||
}
|
||||
if (v >= (field_frequency_max.value() - steps_ ) )
|
||||
field_frequency_max.set_value( v + steps_ );
|
||||
if (v > (field_frequency_max.value() - min_size ) )
|
||||
field_frequency_max.set_value( v + min_size );
|
||||
if( locked_range )
|
||||
field_frequency_max.set_value( v + min_size );
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset
|
||||
field_frequency_min.set_step( steps );
|
||||
|
||||
field_frequency_min.on_select = [this, &nav](NumberField& field) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_min.value()*1000000);
|
||||
new_view->on_changed = [this, &field](rf::Frequency f) {
|
||||
int32_t freq = f / 1000000 ;
|
||||
int32_t steps_ = steps ;
|
||||
if( steps_ < 20 )
|
||||
steps_ = 20 ;
|
||||
if( freq > (7200 - steps_ ) )
|
||||
freq= 7200 - steps_ ;
|
||||
int32_t min_size = steps ;
|
||||
if( locked_range )
|
||||
min_size = search_span ;
|
||||
if( min_size < 2 )
|
||||
min_size = 2 ;
|
||||
if( freq > (7200 - min_size ) )
|
||||
freq = 7200 - min_size ;
|
||||
field_frequency_min.set_value( freq );
|
||||
if( field_frequency_max.value() < ( freq + steps_ ) )
|
||||
field_frequency_max.set_value( freq + steps_ );
|
||||
if( field_frequency_max.value() < ( freq + min_size ) )
|
||||
field_frequency_max.set_value( freq + min_size );
|
||||
this->on_range_changed();
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset
|
||||
field_frequency_max.set_step( steps );
|
||||
field_frequency_max.on_change = [this](int32_t v)
|
||||
{
|
||||
int32_t steps_ = steps ;
|
||||
if( steps_ < 20 )
|
||||
steps_ = 20 ;
|
||||
if( v < steps_ )
|
||||
reset_live_view( true );
|
||||
int32_t min_size = steps ;
|
||||
if( locked_range )
|
||||
min_size = search_span ;
|
||||
if( min_size < 2 )
|
||||
min_size = 2 ;
|
||||
if( v < min_size )
|
||||
{
|
||||
v = steps_ ;
|
||||
v = min_size ;
|
||||
field_frequency_max.set_value( v );
|
||||
}
|
||||
if (v < (field_frequency_min.value() + steps_) )
|
||||
field_frequency_min.set_value(v - steps_);
|
||||
if (v < (field_frequency_min.value() + min_size) )
|
||||
field_frequency_min.set_value(v - min_size);
|
||||
if( locked_range )
|
||||
field_frequency_min.set_value( v - min_size );
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset
|
||||
field_frequency_max.set_step( steps );
|
||||
|
||||
field_frequency_max.on_select = [this, &nav](NumberField& field) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_max.value()*1000000);
|
||||
new_view->on_changed = [this, &field](rf::Frequency f) {
|
||||
int32_t steps_ = steps ;
|
||||
if( steps_ < 20 )
|
||||
steps_ = 20 ;
|
||||
int32_t min_size = steps ;
|
||||
if( locked_range )
|
||||
min_size = search_span ;
|
||||
if( min_size < 2 )
|
||||
min_size = 2 ;
|
||||
int32_t freq = f / 1000000 ;
|
||||
if( freq < 20 )
|
||||
freq = 20 ;
|
||||
if( freq < min_size )
|
||||
freq = min_size ;
|
||||
field_frequency_max.set_value( freq );
|
||||
if( field_frequency_min.value() > ( freq - steps) )
|
||||
field_frequency_min.set_value( freq - steps );
|
||||
if( field_frequency_min.value() > ( freq - min_size) )
|
||||
field_frequency_min.set_value( freq - min_size );
|
||||
this->on_range_changed();
|
||||
};
|
||||
};
|
||||
|
||||
field_lna.set_value(receiver_model.lna());
|
||||
field_lna.on_change = [this](int32_t v)
|
||||
{
|
||||
reset_live_view( true );
|
||||
this->on_lna_changed(v);
|
||||
};
|
||||
field_lna.set_value(receiver_model.lna());
|
||||
|
||||
field_vga.set_value(receiver_model.vga());
|
||||
field_vga.on_change = [this](int32_t v_db)
|
||||
{
|
||||
reset_live_view( true );
|
||||
this->on_vga_changed(v_db);
|
||||
};
|
||||
field_vga.set_value(receiver_model.vga());
|
||||
|
||||
steps_config.set_selected_index(3); //default of 250 Mhz steps
|
||||
steps_config.on_change = [this](size_t n, OptionsField::value_t v)
|
||||
{
|
||||
(void)n;
|
||||
@@ -280,6 +467,67 @@ namespace ui
|
||||
field_frequency_max.set_step( v );
|
||||
steps = v ;
|
||||
};
|
||||
steps_config.set_selected_index(0); //default of 1 Mhz steps
|
||||
|
||||
scan_type.on_change = [this](size_t n, OptionsField::value_t v)
|
||||
{
|
||||
(void)n;
|
||||
fast_scan = v ;
|
||||
};
|
||||
scan_type.set_selected_index(0); // default legacy fast scan
|
||||
|
||||
view_config.on_change = [this](size_t n, OptionsField::value_t v)
|
||||
{
|
||||
(void)n;
|
||||
// clear between changes
|
||||
reset_live_view( true );
|
||||
if( v == 0 )
|
||||
{
|
||||
live_frequency_view = 0 ;
|
||||
level_integration.hidden( true );
|
||||
freq_stats.hidden( true );
|
||||
button_jump.hidden( true );
|
||||
button_rst.hidden( true );
|
||||
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
|
||||
}
|
||||
else if( v == 1 )
|
||||
{
|
||||
display.fill_rectangle( { { 0 , 108 } , { 240 , 24 } } , { 0 , 0 , 0 } );
|
||||
live_frequency_view = 1 ;
|
||||
display.scroll_disable();
|
||||
level_integration.hidden( false );
|
||||
freq_stats.hidden( false );
|
||||
button_jump.hidden( false );
|
||||
button_rst.hidden( false );
|
||||
}
|
||||
else if( v == 2 )
|
||||
{
|
||||
display.fill_rectangle( { { 0 , 108 } , { 240 , 24 } } , { 0 , 0 , 0 } );
|
||||
live_frequency_view = 2 ;
|
||||
display.scroll_disable();
|
||||
level_integration.hidden( false );
|
||||
freq_stats.hidden( false );
|
||||
button_jump.hidden( false );
|
||||
button_rst.hidden( false );
|
||||
}
|
||||
set_dirty();
|
||||
};
|
||||
view_config.set_selected_index(0); //default spectrum
|
||||
|
||||
level_integration.on_change = [this](size_t n, OptionsField::value_t v)
|
||||
{
|
||||
(void)n;
|
||||
reset_live_view( true );
|
||||
live_frequency_integrate = v ;
|
||||
};
|
||||
level_integration.set_selected_index(3); //default integration of ( 3 * old value + new_value ) / 4
|
||||
|
||||
filter_config.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
(void)n;
|
||||
reset_live_view( true );
|
||||
min_color_power = v;
|
||||
};
|
||||
filter_config.set_selected_index(0);
|
||||
|
||||
range_presets.on_change = [this](size_t n, OptionsField::value_t v)
|
||||
{
|
||||
@@ -289,26 +537,57 @@ namespace ui
|
||||
this->on_range_changed();
|
||||
};
|
||||
|
||||
field_marker.on_change = [this](int32_t v)
|
||||
button_marker.on_change = [this]()
|
||||
{
|
||||
PlotMarker(v); // Refresh marker on screen
|
||||
marker = marker + button_marker.get_encoder_delta() * marker_pixel_step ;
|
||||
if( marker < f_min )
|
||||
marker = f_min ;
|
||||
if( marker > f_max )
|
||||
marker = f_max ;
|
||||
button_marker.set_text( to_string_short_freq( marker ) );
|
||||
button_marker.set_encoder_delta( 0 );
|
||||
PlotMarker( marker ); // Refresh marker on screen
|
||||
};
|
||||
|
||||
field_marker.on_select = [this](NumberField &)
|
||||
button_marker.on_select = [this](ButtonWithEncoder &)
|
||||
{
|
||||
f_center = field_marker.value();
|
||||
f_center = f_center * MHZ_DIV;
|
||||
receiver_model.set_tuning_frequency(f_center); // Center tune rx in marker freq.
|
||||
receiver_model.set_tuning_frequency(marker); // Center tune rx in marker freq.
|
||||
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>(); // Jump into audio view
|
||||
};
|
||||
|
||||
field_trigger.set_value(32); // Defaults to 32, as normal triggering resolution
|
||||
field_trigger.on_change = [this](int32_t v)
|
||||
{
|
||||
baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, v);
|
||||
};
|
||||
field_trigger.set_value(32); // Defaults to 32, as normal triggering resolution
|
||||
|
||||
button_range.on_select = [this](Button&) {
|
||||
if( locked_range )
|
||||
{
|
||||
locked_range = false ;
|
||||
button_range.set_style(&style_white);
|
||||
button_range.set_text(" "+to_string_dec_uint(search_span)+" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
locked_range = true ;
|
||||
button_range.set_style(&style_red);
|
||||
button_range.set_text(">"+to_string_dec_uint(search_span)+"<");
|
||||
}
|
||||
};
|
||||
|
||||
button_jump.on_select = [this](Button&) {
|
||||
receiver_model.set_tuning_frequency(max_freq_hold); // Center tune rx in marker freq.
|
||||
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>(); // Jump into audio view
|
||||
};
|
||||
|
||||
button_rst.on_select = [this](Button&) {
|
||||
reset_live_view( true );
|
||||
};
|
||||
|
||||
display.scroll_set_area(109, 319);
|
||||
baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, field_trigger.value()); // trigger:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2020 euquiq
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -36,20 +37,21 @@
|
||||
|
||||
namespace ui
|
||||
{
|
||||
#define LOOKING_GLASS_SLICE_WIDTH 20000000 // Each slice bandwidth 20 MHz
|
||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
||||
#define MHZ_DIV 1000000
|
||||
#define X2_MHZ_DIV 2000000
|
||||
|
||||
class GlassView : public View
|
||||
{
|
||||
public:
|
||||
|
||||
GlassView(NavigationView &nav);
|
||||
|
||||
GlassView( const GlassView &);
|
||||
GlassView& operator=(const GlassView &nav);
|
||||
|
||||
~GlassView();
|
||||
std::string title() const override { return "Looking Glass"; };
|
||||
std::string title() const override { return "LookingGlass"; };
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
@@ -64,15 +66,37 @@ namespace ui
|
||||
rf::Frequency max{};
|
||||
std::string label{};
|
||||
};
|
||||
|
||||
const Style style_white { // free range
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style style_red { // locked range
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
std::vector<preset_entry> presets_db{};
|
||||
|
||||
// Each slice bandwidth 20 MHz and a multiple of 256
|
||||
// since we are using LOOKING_GLASS_SLICE_WIDTH/256 as the each_bin_size
|
||||
// it should also be a multiple of 2 since we are using LOOKING_GLASS_SLICE_WIDTH / 2 as centering freq
|
||||
int64_t LOOKING_GLASS_SLICE_WIDTH = 20000000;
|
||||
|
||||
// frequency rounding helpers
|
||||
int64_t next_mult_of(int64_t num, int64_t multiplier);
|
||||
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
void do_timers();
|
||||
void on_range_changed();
|
||||
void on_lna_changed(int32_t v_db);
|
||||
void on_vga_changed(int32_t v_db);
|
||||
void add_spectrum_pixel(Color color);
|
||||
void reset_live_view( bool clear_screen );
|
||||
void add_spectrum_pixel(uint8_t power);
|
||||
void PlotMarker(rf::Frequency pos);
|
||||
void load_Presets();
|
||||
void txtline_process(std::string& line);
|
||||
@@ -83,22 +107,30 @@ namespace ui
|
||||
rf::Frequency search_span { 0 };
|
||||
rf::Frequency f_center { 0 };
|
||||
rf::Frequency f_center_ini { 0 };
|
||||
rf::Frequency marker { 0 };
|
||||
rf::Frequency marker_pixel_step { 0 };
|
||||
rf::Frequency each_bin_size { LOOKING_GLASS_SLICE_WIDTH / 240 };
|
||||
rf::Frequency each_bin_size { LOOKING_GLASS_SLICE_WIDTH / 256 };
|
||||
rf::Frequency bins_Hz_size { 0 };
|
||||
uint8_t min_color_power { 0 };
|
||||
uint32_t pixel_index { 0 };
|
||||
std::array<Color, 240> spectrum_row = { 0 };
|
||||
std::array<uint8_t, 240> spectrum_data = { 0 };
|
||||
ChannelSpectrumFIFO* fifo { nullptr };
|
||||
uint8_t max_power = 0;
|
||||
int32_t steps = 250 ; // default of 250 Mhz steps
|
||||
int32_t steps = 0 ;
|
||||
uint8_t live_frequency_view = 0 ;
|
||||
int16_t live_frequency_integrate = 3 ;
|
||||
int64_t max_freq_hold = 0 ;
|
||||
int16_t max_freq_power = -1000 ;
|
||||
bool fast_scan = true ; // default to legacy fast scan
|
||||
bool locked_range = false ;
|
||||
|
||||
Labels labels{
|
||||
{{0, 0}, "MIN: MAX: LNA VGA ", Color::light_grey()},
|
||||
{{0, 1 * 16}, " RANGE: FILTER: AMP:", Color::light_grey()},
|
||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
|
||||
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
|
||||
{{0, 3 * 16}, "MARKER: MHz +/- MHz", Color::light_grey()},
|
||||
{{0, 4 * 16}, "RESOLUTION: STEPS:", Color::light_grey()}
|
||||
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
|
||||
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}
|
||||
};
|
||||
|
||||
NumberField field_frequency_min {
|
||||
@@ -125,24 +157,12 @@ namespace ui
|
||||
{ 27 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
Text text_range{
|
||||
Button button_range{
|
||||
{7 * 8, 1 * 16, 4 * 8, 16},
|
||||
""};
|
||||
|
||||
OptionsField steps_config{
|
||||
{ 22 * 8, 4 * 16},
|
||||
4,
|
||||
{
|
||||
{"1", 1},
|
||||
{"50", 50},
|
||||
{"100", 100},
|
||||
{"250", 250},
|
||||
{"500", 500},
|
||||
{"1000", 1000},
|
||||
}};
|
||||
|
||||
OptionsField filter_config{
|
||||
{19 * 8, 1 * 16},
|
||||
{20 * 8, 1 * 16},
|
||||
4,
|
||||
{
|
||||
{"OFF ", 0},
|
||||
@@ -160,24 +180,80 @@ namespace ui
|
||||
{" NONE (WIFI 2.4GHz)", 0},
|
||||
}};
|
||||
|
||||
NumberField field_marker{
|
||||
{7 * 8, 3 * 16},
|
||||
4,
|
||||
{0, 7200},
|
||||
25,
|
||||
' '};
|
||||
|
||||
Text text_marker_pm{
|
||||
{20 * 8, 3 * 16, 2 * 8, 16},
|
||||
""};
|
||||
ButtonWithEncoder button_marker{
|
||||
{7 * 8, 3 * 16 , 10 * 8 , 16},
|
||||
" "
|
||||
};
|
||||
|
||||
NumberField field_trigger{
|
||||
{11 * 8, 4 * 16},
|
||||
{4 * 8, 4 * 16},
|
||||
3,
|
||||
{2, 128},
|
||||
2,
|
||||
' '};
|
||||
|
||||
OptionsField steps_config{
|
||||
{ 13 * 8, 4 * 16},
|
||||
3,
|
||||
{
|
||||
{"1", 1},
|
||||
{"25", 25},
|
||||
{"50", 50},
|
||||
{"100", 100},
|
||||
{"250", 250},
|
||||
{"500", 500},
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField scan_type{
|
||||
{ 17 * 8, 4 * 16},
|
||||
2,
|
||||
{
|
||||
{"F-", true },
|
||||
{"S-", false },
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField view_config{
|
||||
{ 19 * 8, 4 * 16},
|
||||
7,
|
||||
{
|
||||
{"SPCTR-V", 0 },
|
||||
{"LEVEL-V", 1 },
|
||||
{"PEAK-V" , 2 },
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField level_integration{
|
||||
{ 27 * 8, 4 * 16},
|
||||
2,
|
||||
{
|
||||
{"x0", 0 },
|
||||
{"x1", 1 },
|
||||
{"x2", 2 },
|
||||
{"x3", 3 },
|
||||
{"x4", 4 },
|
||||
{"x5", 5 },
|
||||
{"x6", 6 },
|
||||
{"x7", 7 },
|
||||
{"x8", 8 },
|
||||
{"x9", 9 },
|
||||
}};
|
||||
|
||||
Button button_jump {
|
||||
{ 240 - 4 * 8 , 5 * 16 , 4 * 8, 16 },
|
||||
"JMP"
|
||||
};
|
||||
|
||||
Button button_rst {
|
||||
{ 240 - 9 * 8 , 5 * 16 , 4 * 8, 16 },
|
||||
"RST"
|
||||
};
|
||||
|
||||
Text freq_stats{
|
||||
{0 * 8, 5 * 16 , 240 - 10 * 8 , 8 },
|
||||
""
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config {
|
||||
Message::ID::ChannelSpectrumConfig,
|
||||
|
||||
@@ -180,7 +180,10 @@ void MicTXView::rxaudio(bool is_on) {
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
// receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
|
||||
receiver_model.set_tuning_frequency(rx_frequency); // Now with seperate controls!
|
||||
if (bool_same_F_tx_rx_enabled) // when stop TX ,define to which freq RX we return
|
||||
receiver_model.set_tuning_frequency(tx_frequency); // Update freq also for RX = TX
|
||||
else
|
||||
receiver_model.set_tuning_frequency(rx_frequency); // Now with seperate freq controls!
|
||||
receiver_model.set_lna(rx_lna);
|
||||
receiver_model.set_vga(rx_vga);
|
||||
receiver_model.set_rf_amp(rx_amp);
|
||||
@@ -237,6 +240,7 @@ MicTXView::MicTXView(
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
&check_rogerbeep,
|
||||
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
|
||||
&check_rxactive,
|
||||
&field_volume,
|
||||
&field_rxbw,
|
||||
@@ -266,6 +270,7 @@ MicTXView::MicTXView(
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
&check_rogerbeep,
|
||||
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
|
||||
&check_rxactive,
|
||||
&field_volume,
|
||||
&field_rxbw,
|
||||
@@ -332,8 +337,12 @@ MicTXView::MicTXView(
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
tx_frequency = f;
|
||||
if(!rx_enabled)
|
||||
if(!rx_enabled) { // not activated receiver. just update freq TX
|
||||
transmitter_model.set_tuning_frequency(f);
|
||||
} else { // activated receiver.
|
||||
if (bool_same_F_tx_rx_enabled) // user selected common freq- TX = RX
|
||||
receiver_model.set_tuning_frequency(f); //Update common freq also for RX
|
||||
}
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
focused_ui = 0;
|
||||
@@ -341,8 +350,12 @@ MicTXView::MicTXView(
|
||||
auto new_view = nav.push<FrequencyKeypadView>(tx_frequency);
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
tx_frequency = f;
|
||||
if(!rx_enabled)
|
||||
if(!rx_enabled) {
|
||||
transmitter_model.set_tuning_frequency(f);
|
||||
} else {
|
||||
if (bool_same_F_tx_rx_enabled)
|
||||
receiver_model.set_tuning_frequency(f); //Update freq also for RX
|
||||
}
|
||||
this->field_frequency.set_value(f);
|
||||
set_dirty();
|
||||
};
|
||||
@@ -511,6 +524,13 @@ MicTXView::MicTXView(
|
||||
rogerbeep_enabled = v;
|
||||
};
|
||||
|
||||
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
|
||||
bool_same_F_tx_rx_enabled = v;
|
||||
field_rxfrequency.hidden(v); //Hide or show separated freq RX field . (When no hide user can enter down indep. freq for RX)
|
||||
set_dirty(); //Refresh GUI interface
|
||||
receiver_model.set_tuning_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
|
||||
};
|
||||
|
||||
field_va_level.on_change = [this](int32_t v) {
|
||||
va_level = v;
|
||||
vumeter.set_mark(v);
|
||||
@@ -563,12 +583,12 @@ MicTXView::MicTXView(
|
||||
rx_frequency = receiver_model.tuning_frequency();
|
||||
field_rxfrequency.set_value(rx_frequency);
|
||||
field_rxfrequency.set_step(receiver_model.frequency_step());
|
||||
field_rxfrequency.on_change = [this](rf::Frequency f) {
|
||||
field_rxfrequency.on_change = [this](rf::Frequency f) { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
|
||||
rx_frequency = f;
|
||||
if(rx_enabled)
|
||||
receiver_model.set_tuning_frequency(f);
|
||||
};
|
||||
field_rxfrequency.on_edit = [this, &nav]() {
|
||||
field_rxfrequency.on_edit = [this, &nav]() { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
|
||||
focused_ui = 1;
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(rx_frequency);
|
||||
|
||||
@@ -80,6 +80,7 @@ private:
|
||||
bool va_enabled { false };
|
||||
bool ptt_enabled { true };
|
||||
bool rogerbeep_enabled { false };
|
||||
bool bool_same_F_tx_rx_enabled { false };
|
||||
bool rx_enabled { false };
|
||||
uint32_t tone_key_index { };
|
||||
float mic_gain { 1.0 };
|
||||
@@ -123,9 +124,9 @@ private:
|
||||
{ {20 * 8, 10 * 8 }, "DEC:", Color::light_grey() },
|
||||
{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
|
||||
{ { 7 * 8, 23 * 8 }, "VOL:", Color::light_grey() },
|
||||
{ {14 * 8, 23 * 8 }, "RXBW:", Color::light_grey() }, //we remove the label "FM" because we will display all MOD types RX_BW.
|
||||
{ {17 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
|
||||
{ { 5 * 8, 25 * 8 }, "F:", Color::light_grey() },
|
||||
{ {14 * 8, 23 * 8 }, "RXBW:", Color::light_grey() }, //we remove the label "FM" because we will display all MOD types RX_BW.
|
||||
{ {20 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
|
||||
{ { 5 * 8, 25 * 8 }, "F_RX:", Color::light_grey() },
|
||||
{ { 5 * 8, 27 * 8 }, "LNA:", Color::light_grey()},
|
||||
{ {12 * 8, 27 * 8 }, "VGA:", Color::light_grey()},
|
||||
{ {19 * 8, 27 * 8 }, "AMP:", Color::light_grey()}
|
||||
@@ -145,8 +146,8 @@ private:
|
||||
{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
|
||||
{ { (6 * 8)+4, 23 * 8 }, "VOL:", Color::light_grey() },
|
||||
{ {14 * 8, 23 * 8 }, "RXBW:", Color::light_grey() }, //we remove the label "FM" because we will display all MOD types RX_BW.
|
||||
{ {17 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
|
||||
{ { 5 * 8, 25 * 8 }, "F:", Color::light_grey() },
|
||||
{ {20 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
|
||||
{ { 5 * 8, 25 * 8 }, "F_RX:", Color::light_grey() },
|
||||
{ { 5 * 8, 27 * 8 }, "LNA:", Color::light_grey()},
|
||||
{ {12 * 8, 27 * 8 }, "VGA:", Color::light_grey()},
|
||||
{ {19 * 8, 27 * 8 }, "AMP:", Color::light_grey()}
|
||||
@@ -287,7 +288,7 @@ OptionsField options_wm8731_boost_mode {
|
||||
};
|
||||
|
||||
Checkbox check_rogerbeep {
|
||||
{ 3 * 8, ( 16 * 8 ) + 4 },
|
||||
{ 3 * 8, ( 16 * 8 ) + 7 },
|
||||
10,
|
||||
"Roger beep",
|
||||
false
|
||||
@@ -295,11 +296,18 @@ OptionsField options_wm8731_boost_mode {
|
||||
|
||||
Checkbox check_rxactive {
|
||||
{ 3 * 8, ( 21 * 8 ) - 4 },
|
||||
8,
|
||||
18, // it was 8, but if it is string size should be 18
|
||||
"RX audio listening",
|
||||
false
|
||||
};
|
||||
|
||||
Checkbox check_common_freq_tx_rx {
|
||||
{ 18 * 8, ( 16* 8 ) + 7 },
|
||||
8,
|
||||
"F = F_RX",
|
||||
false
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 11* 8, 23 * 8 },
|
||||
2,
|
||||
@@ -319,7 +327,7 @@ OptionsField options_wm8731_boost_mode {
|
||||
};
|
||||
|
||||
NumberField field_squelch {
|
||||
{ 20 * 8, 25 * 8 },
|
||||
{ 23 * 8, 25 * 8 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
@@ -327,7 +335,7 @@ OptionsField options_wm8731_boost_mode {
|
||||
};
|
||||
|
||||
FrequencyField field_rxfrequency {
|
||||
{ 7 * 8, 25 * 8 },
|
||||
{ 10 * 8, 25 * 8 },
|
||||
};
|
||||
|
||||
NumberField field_rxlna {
|
||||
@@ -347,7 +355,7 @@ OptionsField options_wm8731_boost_mode {
|
||||
};
|
||||
|
||||
NumberField field_rxamp {
|
||||
{ 23 * 8, 27 * 8 },
|
||||
{ 24 * 8, 27 * 8 },
|
||||
1,
|
||||
{ 0, 1 },
|
||||
1,
|
||||
|
||||
+1857
-1701
File diff suppressed because it is too large
Load Diff
@@ -39,370 +39,389 @@
|
||||
#include "file.hpp"
|
||||
#include "app_settings.hpp"
|
||||
|
||||
// maximum usable freq
|
||||
#define MAX_UFREQ 7200000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ReconThread {
|
||||
public:
|
||||
ReconThread(freqman_db *database );
|
||||
~ReconThread();
|
||||
class ReconThread {
|
||||
public:
|
||||
ReconThread(freqman_db *database );
|
||||
~ReconThread();
|
||||
|
||||
void set_recon(const bool v);
|
||||
void set_freq_delete(const bool v);
|
||||
bool is_recon();
|
||||
void set_recon(const bool v);
|
||||
void set_freq_delete(const bool v);
|
||||
bool is_recon();
|
||||
|
||||
void set_lock_duration( const uint32_t v );
|
||||
uint32_t get_lock_duration();
|
||||
void set_lock_nb_match( const uint32_t v );
|
||||
void set_match_mode( const uint32_t v );
|
||||
uint32_t get_lock_nb_match();
|
||||
void set_lock_duration( const uint32_t v );
|
||||
uint32_t get_lock_duration();
|
||||
void set_lock_nb_match( const uint32_t v );
|
||||
void set_match_mode( const uint32_t v );
|
||||
uint32_t get_lock_nb_match();
|
||||
|
||||
void set_freq_lock(const uint32_t v);
|
||||
uint32_t is_freq_lock();
|
||||
int64_t get_current_freq();
|
||||
void set_freq_lock(const uint32_t v);
|
||||
uint32_t is_freq_lock();
|
||||
int64_t get_current_freq();
|
||||
|
||||
void set_stepper(const int64_t v);
|
||||
void set_stepper(const int64_t v);
|
||||
void set_index_stepper(const int64_t v);
|
||||
|
||||
void change_recon_direction();
|
||||
bool get_recon_direction();
|
||||
void set_recon_direction( const bool v);
|
||||
void change_recon_direction();
|
||||
bool get_recon_direction();
|
||||
void set_recon_direction( const bool v);
|
||||
|
||||
void set_continuous(const bool v);
|
||||
void set_continuous(const bool v);
|
||||
|
||||
void set_default_modulation( freqman_index_t index );
|
||||
freqman_index_t get_current_modulation();
|
||||
void set_default_bandwidth( freqman_index_t index );
|
||||
freqman_index_t get_current_bandwidth();
|
||||
void set_default_step( freqman_index_t index );
|
||||
void set_freq_index( int16_t index );
|
||||
int16_t get_freq_index();
|
||||
void set_default_modulation( freqman_index_t index );
|
||||
freqman_index_t get_current_modulation();
|
||||
void set_default_bandwidth( freqman_index_t index );
|
||||
freqman_index_t get_current_bandwidth();
|
||||
void set_default_step( freqman_index_t index );
|
||||
void set_freq_index( int16_t index );
|
||||
int16_t get_freq_index();
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
ReconThread(const ReconThread&) = delete;
|
||||
ReconThread(ReconThread&&) = delete;
|
||||
ReconThread& operator=(const ReconThread&) = delete;
|
||||
ReconThread& operator=(ReconThread&&) = delete;
|
||||
ReconThread(const ReconThread&) = delete;
|
||||
ReconThread(ReconThread&&) = delete;
|
||||
ReconThread& operator=(const ReconThread&) = delete;
|
||||
ReconThread& operator=(ReconThread&&) = delete;
|
||||
|
||||
private:
|
||||
freqman_db &frequency_list_ ;
|
||||
Thread* thread { nullptr };
|
||||
int64_t freq = 0 ;
|
||||
uint32_t step = 0 ;
|
||||
freqman_index_t def_modulation = 0 ;
|
||||
freqman_index_t def_bandwidth = 0 ;
|
||||
freqman_index_t def_step = 0 ;
|
||||
tone_index tone = 0 ;
|
||||
freqman_entry last_entry = { } ;
|
||||
int16_t frequency_index = 0 ;
|
||||
private:
|
||||
freqman_db &frequency_list_ ;
|
||||
Thread* thread { nullptr };
|
||||
int64_t freq = 0 ;
|
||||
uint32_t step = 0 ;
|
||||
freqman_index_t def_modulation = 0 ;
|
||||
freqman_index_t def_bandwidth = 0 ;
|
||||
freqman_index_t def_step = 0 ;
|
||||
tone_index tone = 0 ;
|
||||
freqman_entry last_entry = { } ;
|
||||
int16_t frequency_index = 0 ;
|
||||
|
||||
bool _recon { true };
|
||||
bool _freq_delete { false };
|
||||
bool _fwd { true };
|
||||
bool _continuous { true };
|
||||
int64_t _stepper { 0 };
|
||||
int32_t _freq_lock { 0 };
|
||||
uint32_t _lock_duration { 50 };
|
||||
uint32_t _lock_nb_match { 10 };
|
||||
static msg_t static_fn(void* arg);
|
||||
};
|
||||
bool _recon { true };
|
||||
bool _freq_delete { false };
|
||||
bool _fwd { true };
|
||||
bool _continuous { true };
|
||||
bool entry_has_changed = false ;
|
||||
int64_t _stepper { 0 };
|
||||
int64_t _index_stepper { 0 };
|
||||
int32_t _freq_lock { 0 };
|
||||
uint32_t _lock_duration { 50 };
|
||||
uint32_t _lock_nb_match { 10 };
|
||||
static msg_t static_fn(void* arg);
|
||||
};
|
||||
|
||||
class ReconView : public View {
|
||||
public:
|
||||
ReconView(NavigationView& nav);
|
||||
~ReconView();
|
||||
class ReconView : public View {
|
||||
public:
|
||||
ReconView(NavigationView& nav);
|
||||
~ReconView();
|
||||
|
||||
void focus() override;
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq( int64_t f );
|
||||
const Style style_grey { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
|
||||
const Style style_grey { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
const Style style_white { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style style_white { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
const Style style_yellow { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
|
||||
const Style style_yellow { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
const Style style_blue { // quick recon, wait == 0
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
};
|
||||
|
||||
const Style style_blue { // quick recon, wait == 0
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
};
|
||||
std::string title() const override { return "Recon"; };
|
||||
|
||||
std::string title() const override { return "Recon"; };
|
||||
//void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
//void set_parent_rect(const Rect new_parent_rect) override;
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void start_recon_thread();
|
||||
size_t change_mode( freqman_index_t mod_type);
|
||||
void show_max( bool refresh_display = false );
|
||||
void recon_pause();
|
||||
void recon_resume();
|
||||
void user_pause();
|
||||
void user_resume();
|
||||
void frequency_file_load( bool stop_all_before = false);
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
void set_display_freq( int64_t freq );
|
||||
void handle_retune( int64_t freq , uint32_t index );
|
||||
bool check_sd_card();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
|
||||
void start_recon_thread();
|
||||
size_t change_mode( freqman_index_t mod_type);
|
||||
void show_max( bool refresh_display = false );
|
||||
void recon_pause();
|
||||
void recon_resume();
|
||||
void user_pause();
|
||||
void user_resume();
|
||||
void frequency_file_load( bool stop_all_before = false);
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
void set_display_freq( int64_t freq );
|
||||
void handle_retune( int64_t freq , uint32_t index );
|
||||
bool check_sd_card();
|
||||
jammer::jammer_range_t frequency_range { false, 0, MAX_UFREQ }; //perfect for manual recon task too...
|
||||
int32_t squelch { 0 };
|
||||
int32_t db { 0 };
|
||||
int32_t timer { 0 };
|
||||
int32_t wait { 5000 }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
uint32_t lock_wait { 500 }; // in msec. Represent the maximum amount of time we will wait for a lock to complete before switching to next
|
||||
int32_t def_step { 0 };
|
||||
freqman_db frequency_list = { };
|
||||
uint32_t current_index { 0 };
|
||||
bool userpause { false };
|
||||
bool continuous_lock { false };
|
||||
std::string input_file = { "RECON" };
|
||||
std::string output_file = { "RECON_RESULTS" };
|
||||
bool autosave = { true };
|
||||
bool autostart = { true };
|
||||
bool continuous = { true };
|
||||
bool filedelete = { true };
|
||||
bool load_freqs = { true };
|
||||
bool load_ranges = { true };
|
||||
bool load_hamradios = { true };
|
||||
bool update_ranges = { true };
|
||||
bool fwd = { true };
|
||||
uint32_t recon_lock_nb_match = { 10 };
|
||||
uint32_t recon_lock_duration = { 50 };
|
||||
uint32_t recon_match_mode = { 0 };
|
||||
bool scanner_mode { false };
|
||||
bool manual_mode { false };
|
||||
bool sd_card_mounted = false ;
|
||||
int32_t volume = 40 ;
|
||||
|
||||
jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual recon task too...
|
||||
int32_t squelch { 0 };
|
||||
int32_t db { 0 };
|
||||
int32_t timer { 0 };
|
||||
int32_t wait { 5000 }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
uint32_t lock_wait { 500 }; // in msec. Represent the maximum amount of time we will wait for a lock to complete before switching to next
|
||||
int32_t def_step { 0 };
|
||||
freqman_db frequency_list = { };
|
||||
uint32_t current_index { 0 };
|
||||
bool userpause { false };
|
||||
bool continuous_lock { false };
|
||||
std::string input_file = { "RECON" };
|
||||
std::string output_file = { "RECON_RESULTS" };
|
||||
bool autosave = { true };
|
||||
bool autostart = { true };
|
||||
bool continuous = { true };
|
||||
bool filedelete = { true };
|
||||
bool load_freqs = { true };
|
||||
bool load_ranges = { true };
|
||||
bool load_hamradios = { true };
|
||||
bool update_ranges = { true };
|
||||
bool fwd = { true };
|
||||
// maximum usable freq
|
||||
long long int MAX_UFREQ = { 7200000000 };
|
||||
uint32_t recon_lock_nb_match = { 10 };
|
||||
uint32_t recon_lock_duration = { 50 };
|
||||
uint32_t recon_match_mode = { 0 };
|
||||
bool scanner_mode { false };
|
||||
bool manual_mode { false };
|
||||
bool sd_card_mounted = false ;
|
||||
int32_t volume = 40 ;
|
||||
Labels labels
|
||||
{
|
||||
{ { 0 * 8 , 0 * 16 }, "LNA: VGA: AMP: VOL: ", Color::light_grey() },
|
||||
{ { 0 * 8 , 1 * 16 }, "BW: SQ: W,L: , ", Color::light_grey() },
|
||||
{ { 3 * 8 , 10 * 16 }, "START END MANUAL", Color::light_grey() },
|
||||
{ { 0 * 8 , (26 * 8) + 4 }, "MODE:", Color::light_grey() },
|
||||
{ { 11 * 8 , (26 * 8) + 4 }, "STEP:", Color::light_grey() },
|
||||
};
|
||||
|
||||
Labels labels
|
||||
{
|
||||
{ { 0 * 8 , 0 * 16 }, "LNA: VGA: AMP: VOL: ", Color::light_grey() },
|
||||
{ { 0 * 8 , 1 * 16 }, "BW: SQ: W,L: , ", Color::light_grey() },
|
||||
{ { 3 * 8 , 10 * 16 }, "START END MANUAL", Color::light_grey() },
|
||||
{ { 0 * 8 , (26 * 8) + 4 }, "MODE:", Color::light_grey() },
|
||||
{ { 11 * 8 , (26 * 8) + 4 }, "STEP:", Color::light_grey() },
|
||||
};
|
||||
LNAGainField field_lna {
|
||||
{ 4 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
{ 4 * 8, 0 * 16 }
|
||||
};
|
||||
VGAGainField field_vga {
|
||||
{ 11 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 11 * 8, 0 * 16 }
|
||||
};
|
||||
RFAmpField field_rf_amp {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
NumberField field_volume {
|
||||
{ 24 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 24 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
OptionsField field_bw {
|
||||
{ 3 * 8, 1 * 16 },
|
||||
6,
|
||||
{ }
|
||||
};
|
||||
|
||||
OptionsField field_bw {
|
||||
{ 3 * 8, 1 * 16 },
|
||||
6,
|
||||
{ }
|
||||
};
|
||||
NumberField field_squelch {
|
||||
{ 12 * 8, 1 * 16 },
|
||||
3,
|
||||
{ -90, 20 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_squelch {
|
||||
{ 12 * 8, 1 * 16 },
|
||||
3,
|
||||
{ -90, 20 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
NumberField field_wait {
|
||||
{ 20 * 8, 1 * 16 },
|
||||
5,
|
||||
{ -9000, 9000 },
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_wait {
|
||||
{ 20 * 8, 1 * 16 },
|
||||
5,
|
||||
{ -9000, 9000 },
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
NumberField field_lock_wait {
|
||||
{ 26 * 8, 1 * 16 },
|
||||
4,
|
||||
{ 100 , 9000 },
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_lock_wait {
|
||||
{ 26 * 8, 1 * 16 },
|
||||
4,
|
||||
{ 100 , 9000 },
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
RSSI rssi {
|
||||
{ 0 * 16, 2 * 16, 240 - 8 * 8 + 4 , 16 },
|
||||
};
|
||||
|
||||
RSSI rssi {
|
||||
{ 0 * 16, 2 * 16, 240 - 4 * 8 , 16 },
|
||||
};
|
||||
ButtonWithEncoder text_cycle {
|
||||
{ 0, 3 * 16, 4 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_cycle {
|
||||
{ 0, 3 * 16, 3 * 8, 16 },
|
||||
};
|
||||
Text text_max {
|
||||
{ 4 * 8, 3 * 16, 240 - 7 * 8 - 4 * 8 , 16 },
|
||||
};
|
||||
|
||||
Text text_max {
|
||||
{ 3 * 8, 3 * 16, 20 * 8 , 16 },
|
||||
};
|
||||
Text desc_cycle {
|
||||
{0, 4 * 16, 240 , 16 },
|
||||
};
|
||||
|
||||
Text desc_cycle {
|
||||
{0, 4 * 16, 240 , 16 },
|
||||
};
|
||||
/* BigFrequency big_display { //Show frequency in glamour
|
||||
{ 4, 7 * 16 - 8 , 28 * 8, 52 },
|
||||
0
|
||||
}; */
|
||||
|
||||
/* BigFrequency big_display { //Show frequency in glamour
|
||||
{ 4, 7 * 16 - 8 , 28 * 8, 52 },
|
||||
0
|
||||
}; */
|
||||
Text big_display { //Show frequency in text mode
|
||||
{ 0, 5 * 16 , 23 * 8, 16 },
|
||||
};
|
||||
|
||||
Text big_display { //Show frequency in text mode
|
||||
{ 0, 5 * 16 , 21 * 8, 16 },
|
||||
};
|
||||
Text freq_stats { //Show frequency stats in text mode
|
||||
{ 0, 6 * 16 , 23 * 8, 16 },
|
||||
};
|
||||
|
||||
Text freq_stats { //Show frequency stats in text mode
|
||||
{ 0, 6 * 16 , 21 * 8, 16 },
|
||||
};
|
||||
// TIMER: 9999
|
||||
Text text_timer { //Show frequency stats in text mode
|
||||
{ 0, 7 * 16 , 11 * 8, 16 },
|
||||
};
|
||||
|
||||
Text text_timer { //Show frequency stats in text mode
|
||||
{ 0, 7 * 16 , 21 * 8, 16 },
|
||||
};
|
||||
// T: Senn. 32.000k
|
||||
Text text_ctcss {
|
||||
{ 12 * 8 + 4, 7 * 16 , 14 * 8, 1 * 8 },
|
||||
""
|
||||
};
|
||||
|
||||
Button button_recon_setup {
|
||||
{ 240 - 4 * 8 , 2 * 16 , 4 * 8, 28 },
|
||||
"OPT"
|
||||
};
|
||||
Button button_recon_setup {
|
||||
{ 240 - 7 * 8 , 2 * 16 , 7 * 8, 28 },
|
||||
"CONFIG"
|
||||
};
|
||||
|
||||
Button button_looking_glass {
|
||||
{ 240 - 5 * 8 , 6 * 16 - 4 , 5 * 8, 28 },
|
||||
"GLASS"
|
||||
};
|
||||
Button button_looking_glass {
|
||||
{ 240 - 7 * 8 , 5 * 16 , 7 * 8, 28 },
|
||||
"GLASS"
|
||||
};
|
||||
|
||||
Button button_scanner_mode {
|
||||
{ 240 - 8 * 8 , 8 * 16 , 8 * 8, 28 },
|
||||
"RECON"
|
||||
};
|
||||
// Button can be RECON or SCANNER
|
||||
Button button_scanner_mode {
|
||||
{ 240 - 7 * 8 , 8 * 16 , 7 * 8, 28 },
|
||||
"RECON"
|
||||
};
|
||||
|
||||
Text file_name { //Show file used
|
||||
{ 0 , 8 * 16 + 6 , 21 * 8, 16 },
|
||||
};
|
||||
Text file_name { //Show file used
|
||||
{ 0 , 8 * 16 + 6 , 240 - 7 * 8, 16 },
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_manual_start {
|
||||
{ 0 * 8, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
ButtonWithEncoder button_manual_start {
|
||||
{ 0 * 8, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_manual_end {
|
||||
{ 12 * 8 - 6, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
ButtonWithEncoder button_manual_end {
|
||||
{ 12 * 8 - 6, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
|
||||
Button button_manual_recon {
|
||||
{ 23 * 8 - 3, 11 * 16, 7 * 8 , 28 },
|
||||
"SEARCH"
|
||||
};
|
||||
Button button_manual_recon {
|
||||
{ 23 * 8 - 3, 11 * 16, 7 * 8 , 28 },
|
||||
"SEARCH"
|
||||
};
|
||||
|
||||
OptionsField field_mode {
|
||||
{ 5 * 8, (26 * 8) + 4 },
|
||||
6,
|
||||
{
|
||||
}
|
||||
};
|
||||
OptionsField field_mode {
|
||||
{ 5 * 8, (26 * 8) + 4 },
|
||||
6,
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField step_mode {
|
||||
{ 17 * 8, (26 * 8) + 4 },
|
||||
12,
|
||||
{
|
||||
}
|
||||
};
|
||||
OptionsField step_mode {
|
||||
{ 17 * 8, (26 * 8) + 4 },
|
||||
12,
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_pause {
|
||||
{ 0, (15 * 16) - 4, 72, 28 },
|
||||
"PAUSE"
|
||||
};
|
||||
ButtonWithEncoder button_pause {
|
||||
{ 0, (15 * 16) - 4, 72, 28 },
|
||||
"PAUSE"
|
||||
};
|
||||
|
||||
|
||||
Button button_audio_app {
|
||||
{ 84, (15 * 16) - 4, 72, 28 },
|
||||
"AUDIO"
|
||||
};
|
||||
Button button_audio_app {
|
||||
{ 84, (15 * 16) - 4, 72, 28 },
|
||||
"AUDIO"
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_add {
|
||||
{ 168, (15 * 16) - 4, 72, 28 },
|
||||
"<STORE>"
|
||||
};
|
||||
ButtonWithEncoder button_add {
|
||||
{ 168, (15 * 16) - 4, 72, 28 },
|
||||
"<STORE>"
|
||||
};
|
||||
|
||||
Button button_dir {
|
||||
{ 0, (35 * 8) - 4, 34, 28 },
|
||||
"FW>"
|
||||
};
|
||||
Button button_dir {
|
||||
{ 0, (35 * 8) - 4, 34, 28 },
|
||||
"FW>"
|
||||
};
|
||||
|
||||
Button button_restart {
|
||||
{ 38, (35 * 8) - 4, 34, 28 },
|
||||
"RST"
|
||||
};
|
||||
Button button_restart {
|
||||
{ 38, (35 * 8) - 4, 34, 28 },
|
||||
"RST"
|
||||
};
|
||||
|
||||
|
||||
Button button_mic_app {
|
||||
{ 84, (35 * 8) - 4, 72, 28 },
|
||||
"MIC TX"
|
||||
};
|
||||
Button button_mic_app {
|
||||
{ 84, (35 * 8) - 4, 72, 28 },
|
||||
"MIC TX"
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_remove {
|
||||
{ 168, (35 * 8) - 4, 72, 28 },
|
||||
"<REMOVE>"
|
||||
};
|
||||
ButtonWithEncoder button_remove {
|
||||
{ 168, (35 * 8) - 4, 72, 28 },
|
||||
"<REMOVE>"
|
||||
};
|
||||
|
||||
std::unique_ptr<ReconThread> recon_thread { };
|
||||
std::unique_ptr<ReconThread> recon_thread { };
|
||||
|
||||
MessageHandlerRegistration message_handler_retune {
|
||||
Message::ID::Retune,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const RetuneMessage*>(p);
|
||||
this->handle_retune(message.freq,message.range);
|
||||
}
|
||||
};
|
||||
MessageHandlerRegistration message_handler_coded_squelch {
|
||||
Message::ID::CodedSquelch,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||
this->handle_coded_squelch(message.value);
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
// app save settings
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
};
|
||||
MessageHandlerRegistration message_handler_retune {
|
||||
Message::ID::Retune,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const RetuneMessage*>(p);
|
||||
this->handle_retune(message.freq,message.range);
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
// app save settings
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
@@ -34,268 +34,268 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume )
|
||||
{
|
||||
File settings_file;
|
||||
size_t length, file_position = 0;
|
||||
char * pos;
|
||||
char * line_start;
|
||||
char * line_end;
|
||||
char file_data[257];
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume )
|
||||
{
|
||||
File settings_file;
|
||||
size_t length, file_position = 0;
|
||||
char * pos;
|
||||
char * line_start;
|
||||
char * line_end;
|
||||
char file_data[257];
|
||||
|
||||
uint32_t it = 0 ;
|
||||
uint32_t nb_params = 9 ;
|
||||
std::string params[ 9 ];
|
||||
uint32_t it = 0 ;
|
||||
uint32_t nb_params = 9 ;
|
||||
std::string params[ 9 ];
|
||||
|
||||
bool check_sd_card = (sd_card::status() == sd_card::Status::Mounted) ? true : false ;
|
||||
bool check_sd_card = (sd_card::status() == sd_card::Status::Mounted) ? true : false ;
|
||||
|
||||
if( check_sd_card )
|
||||
{
|
||||
auto result = settings_file.open( source );
|
||||
if( !result.is_valid() )
|
||||
{
|
||||
while( it < nb_params )
|
||||
{
|
||||
// Read a 256 bytes block from file
|
||||
settings_file.seek(file_position);
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = settings_file.read(file_data, 256);
|
||||
if (read_size.is_error())
|
||||
break ;
|
||||
file_position += 256;
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
pos=line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1 ;
|
||||
params[ it ] = string( pos , length );
|
||||
it ++ ;
|
||||
line_start = line_end + 1;
|
||||
pos=line_start ;
|
||||
if (line_start - file_data >= 256)
|
||||
break;
|
||||
if( it >= nb_params )
|
||||
break ;
|
||||
}
|
||||
if (read_size.value() != 256)
|
||||
break; // End of file
|
||||
if( check_sd_card )
|
||||
{
|
||||
auto result = settings_file.open( source );
|
||||
if( !result.is_valid() )
|
||||
{
|
||||
while( it < nb_params )
|
||||
{
|
||||
// Read a 256 bytes block from file
|
||||
settings_file.seek(file_position);
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = settings_file.read(file_data, 256);
|
||||
if (read_size.is_error())
|
||||
break ;
|
||||
file_position += 256;
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
pos=line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1 ;
|
||||
params[ it ] = string( pos , length );
|
||||
it ++ ;
|
||||
line_start = line_end + 1;
|
||||
pos=line_start ;
|
||||
if (line_start - file_data >= 256)
|
||||
break;
|
||||
if( it >= nb_params )
|
||||
break ;
|
||||
}
|
||||
if (read_size.value() != 256)
|
||||
break; // End of file
|
||||
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if( it > 0 )
|
||||
input_file = params[ 0 ];
|
||||
else
|
||||
input_file = "RECON" ;
|
||||
if( it > 0 )
|
||||
input_file = params[ 0 ];
|
||||
else
|
||||
input_file = "RECON" ;
|
||||
|
||||
if( it > 1 )
|
||||
output_file= params[ 1 ];
|
||||
else
|
||||
output_file = "RECON_RESULTS" ;
|
||||
if( it > 1 )
|
||||
output_file= params[ 1 ];
|
||||
else
|
||||
output_file = "RECON_RESULTS" ;
|
||||
|
||||
if( it > 2 )
|
||||
recon_lock_duration = strtoll( params[ 2 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_duration = 50 ;
|
||||
if( it > 2 )
|
||||
recon_lock_duration = strtoll( params[ 2 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_duration = 50 ;
|
||||
|
||||
if( it > 3 )
|
||||
recon_lock_nb_match = strtoll( params[ 3 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_nb_match = 10 ;
|
||||
if( it > 3 )
|
||||
recon_lock_nb_match = strtoll( params[ 3 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_nb_match = 10 ;
|
||||
|
||||
if( it > 4 )
|
||||
recon_squelch_level = strtoll( params[ 4 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_squelch_level = -14 ;
|
||||
if( it > 4 )
|
||||
recon_squelch_level = strtoll( params[ 4 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_squelch_level = -14 ;
|
||||
|
||||
if( it > 5 )
|
||||
recon_match_mode = strtoll( params[ 5 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_match_mode = 0 ;
|
||||
if( it > 5 )
|
||||
recon_match_mode = strtoll( params[ 5 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_match_mode = 0 ;
|
||||
|
||||
if( it > 6 )
|
||||
wait = strtoll( params[ 6 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
wait = 5000 ;
|
||||
if( it > 6 )
|
||||
wait = strtoll( params[ 6 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
wait = 5000 ;
|
||||
|
||||
if( it > 7 )
|
||||
lock_wait = strtoll( params[ 7 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
lock_wait = 1000 ;
|
||||
if( it > 7 )
|
||||
lock_wait = strtoll( params[ 7 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
lock_wait = 1000 ;
|
||||
|
||||
if( it > 8 )
|
||||
volume = strtoll( params[ 8 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
volume = 40 ;
|
||||
if( it > 8 )
|
||||
volume = strtoll( params[ 8 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
volume = 40 ;
|
||||
|
||||
if( it < nb_params )
|
||||
{
|
||||
/* bad number of params, signal defaults */
|
||||
return false ;
|
||||
}
|
||||
return true ;
|
||||
}
|
||||
if( it < nb_params )
|
||||
{
|
||||
/* bad number of params, signal defaults */
|
||||
return false ;
|
||||
}
|
||||
return true ;
|
||||
}
|
||||
|
||||
bool ReconSetupSaveStrings( std::string dest, std::string input_file , std::string output_file , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume )
|
||||
{
|
||||
File settings_file;
|
||||
bool ReconSetupSaveStrings( std::string dest, std::string input_file , std::string output_file , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume )
|
||||
{
|
||||
File settings_file;
|
||||
|
||||
auto result = settings_file.create( dest );
|
||||
if( result.is_valid() )
|
||||
return false ;
|
||||
settings_file.write_line( input_file );
|
||||
settings_file.write_line( output_file );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_duration ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_nb_match ) );
|
||||
settings_file.write_line( to_string_dec_int( recon_squelch_level ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_match_mode ) );
|
||||
settings_file.write_line( to_string_dec_int( wait ) );
|
||||
settings_file.write_line( to_string_dec_uint( lock_wait ) );
|
||||
settings_file.write_line( to_string_dec_int( volume ) );
|
||||
return true ;
|
||||
}
|
||||
auto result = settings_file.create( dest );
|
||||
if( result.is_valid() )
|
||||
return false ;
|
||||
settings_file.write_line( input_file );
|
||||
settings_file.write_line( output_file );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_duration ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_nb_match ) );
|
||||
settings_file.write_line( to_string_dec_int( recon_squelch_level ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_match_mode ) );
|
||||
settings_file.write_line( to_string_dec_int( wait ) );
|
||||
settings_file.write_line( to_string_dec_uint( lock_wait ) );
|
||||
settings_file.write_line( to_string_dec_int( volume ) );
|
||||
return true ;
|
||||
}
|
||||
|
||||
ReconSetupViewMain::ReconSetupViewMain( NavigationView &nav , Rect parent_rect , std::string input_file , std::string output_file ) : View( parent_rect ) , _input_file { input_file } , _output_file { output_file }
|
||||
{
|
||||
hidden(true);
|
||||
add_children({
|
||||
&button_load_freqs,
|
||||
&text_input_file,
|
||||
&button_save_freqs,
|
||||
&button_output_file,
|
||||
&checkbox_autosave_freqs,
|
||||
&checkbox_autostart_recon,
|
||||
&checkbox_continuous,
|
||||
&checkbox_clear_output
|
||||
});
|
||||
ReconSetupViewMain::ReconSetupViewMain( NavigationView &nav , Rect parent_rect , std::string input_file , std::string output_file ) : View( parent_rect ) , _input_file { input_file } , _output_file { output_file }
|
||||
{
|
||||
hidden(true);
|
||||
add_children({
|
||||
&button_load_freqs,
|
||||
&text_input_file,
|
||||
&button_save_freqs,
|
||||
&button_output_file,
|
||||
&checkbox_autosave_freqs,
|
||||
&checkbox_autostart_recon,
|
||||
&checkbox_continuous,
|
||||
&checkbox_clear_output
|
||||
});
|
||||
|
||||
checkbox_autosave_freqs.set_value( persistent_memory::recon_autosave_freqs() );
|
||||
checkbox_autostart_recon.set_value( persistent_memory::recon_autostart_recon() );
|
||||
checkbox_continuous.set_value( persistent_memory::recon_continuous() );
|
||||
checkbox_clear_output.set_value( persistent_memory::recon_clear_output() );
|
||||
checkbox_autosave_freqs.set_value( persistent_memory::recon_autosave_freqs() );
|
||||
checkbox_autostart_recon.set_value( persistent_memory::recon_autostart_recon() );
|
||||
checkbox_continuous.set_value( persistent_memory::recon_continuous() );
|
||||
checkbox_clear_output.set_value( persistent_memory::recon_clear_output() );
|
||||
|
||||
text_input_file.set( _input_file );
|
||||
button_output_file.set_text( _output_file );
|
||||
text_input_file.set( _input_file );
|
||||
button_output_file.set_text( _output_file );
|
||||
|
||||
button_load_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
// get the filename without txt extension so we can use load_freqman_file fcn
|
||||
_input_file = new_file_path.stem().string();
|
||||
text_input_file.set( _input_file );
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
};
|
||||
};
|
||||
button_load_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
// get the filename without txt extension so we can use load_freqman_file fcn
|
||||
_input_file = new_file_path.stem().string();
|
||||
text_input_file.set( _input_file );
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
button_save_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
_output_file = new_file_path.stem().string();
|
||||
button_output_file.set_text( _output_file );
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
};
|
||||
};
|
||||
button_save_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
_output_file = new_file_path.stem().string();
|
||||
button_output_file.set_text( _output_file );
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
button_output_file.on_select =[this, &nav](Button&) {
|
||||
text_prompt( nav, _output_file , 28 ,
|
||||
[this](std::string& buffer) {
|
||||
_output_file = buffer ;
|
||||
button_output_file.set_text( _output_file );
|
||||
} );
|
||||
};
|
||||
};
|
||||
button_output_file.on_select =[this, &nav](Button&) {
|
||||
text_prompt( nav, _output_file , 28 ,
|
||||
[this](std::string& buffer) {
|
||||
_output_file = buffer ;
|
||||
button_output_file.set_text( _output_file );
|
||||
} );
|
||||
};
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::Save( std::string &input_file , std::string &output_file ) {
|
||||
persistent_memory::set_recon_autosave_freqs(checkbox_autosave_freqs.value());
|
||||
persistent_memory::set_recon_autostart_recon(checkbox_autostart_recon.value());
|
||||
persistent_memory::set_recon_continuous(checkbox_continuous.value());
|
||||
persistent_memory::set_recon_clear_output(checkbox_clear_output.value());
|
||||
input_file=_input_file ;
|
||||
output_file=_output_file ;
|
||||
};
|
||||
void ReconSetupViewMore::Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode ) {
|
||||
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
|
||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
recon_lock_duration = field_recon_lock_duration.value();
|
||||
recon_lock_nb_match = field_recon_lock_nb_match.value();
|
||||
recon_match_mode = field_recon_match_mode . selected_index_value() ;
|
||||
};
|
||||
void ReconSetupViewMain::Save( std::string &input_file , std::string &output_file ) {
|
||||
persistent_memory::set_recon_autosave_freqs(checkbox_autosave_freqs.value());
|
||||
persistent_memory::set_recon_autostart_recon(checkbox_autostart_recon.value());
|
||||
persistent_memory::set_recon_continuous(checkbox_continuous.value());
|
||||
persistent_memory::set_recon_clear_output(checkbox_clear_output.value());
|
||||
input_file=_input_file ;
|
||||
output_file=_output_file ;
|
||||
};
|
||||
void ReconSetupViewMore::Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode ) {
|
||||
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
|
||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
recon_lock_duration = field_recon_lock_duration.value();
|
||||
recon_lock_nb_match = field_recon_lock_nb_match.value();
|
||||
recon_match_mode = field_recon_match_mode . selected_index_value() ;
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::focus() {
|
||||
button_load_freqs.focus();
|
||||
}
|
||||
void ReconSetupViewMain::focus() {
|
||||
button_load_freqs.focus();
|
||||
}
|
||||
|
||||
ReconSetupViewMore::ReconSetupViewMore( NavigationView &nav , Rect parent_rect , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , uint32_t recon_match_mode ) : View( parent_rect ), _recon_lock_duration { recon_lock_duration } , _recon_lock_nb_match { recon_lock_nb_match } , _recon_match_mode { recon_match_mode }
|
||||
{
|
||||
(void)nav;
|
||||
hidden(true);
|
||||
ReconSetupViewMore::ReconSetupViewMore( NavigationView &nav , Rect parent_rect , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , uint32_t recon_match_mode ) : View( parent_rect ), _recon_lock_duration { recon_lock_duration } , _recon_lock_nb_match { recon_lock_nb_match } , _recon_match_mode { recon_match_mode }
|
||||
{
|
||||
(void)nav;
|
||||
hidden(true);
|
||||
|
||||
add_children({
|
||||
&checkbox_load_freqs,
|
||||
&checkbox_load_ranges,
|
||||
&checkbox_load_hamradios,
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&text_recon_lock_duration,
|
||||
&field_recon_lock_duration,
|
||||
&text_recon_lock_nb,
|
||||
&field_recon_lock_nb_match,
|
||||
&field_recon_match_mode,
|
||||
});
|
||||
add_children({
|
||||
&checkbox_load_freqs,
|
||||
&checkbox_load_ranges,
|
||||
&checkbox_load_hamradios,
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&text_recon_lock_duration,
|
||||
&field_recon_lock_duration,
|
||||
&text_recon_lock_nb,
|
||||
&field_recon_lock_nb_match,
|
||||
&field_recon_match_mode,
|
||||
});
|
||||
|
||||
checkbox_load_freqs.set_value( persistent_memory::recon_load_freqs() );
|
||||
checkbox_load_ranges.set_value( persistent_memory::recon_load_ranges() );
|
||||
checkbox_load_hamradios.set_value( persistent_memory::recon_load_hamradios() );
|
||||
checkbox_update_ranges_when_recon.set_value( persistent_memory::recon_update_ranges_when_recon() );
|
||||
field_recon_lock_duration.set_value( _recon_lock_duration );
|
||||
field_recon_lock_nb_match.set_value( _recon_lock_nb_match );
|
||||
field_recon_match_mode.set_by_value( _recon_match_mode );
|
||||
};
|
||||
checkbox_load_freqs.set_value( persistent_memory::recon_load_freqs() );
|
||||
checkbox_load_ranges.set_value( persistent_memory::recon_load_ranges() );
|
||||
checkbox_load_hamradios.set_value( persistent_memory::recon_load_hamradios() );
|
||||
checkbox_update_ranges_when_recon.set_value( persistent_memory::recon_update_ranges_when_recon() );
|
||||
field_recon_lock_duration.set_value( _recon_lock_duration );
|
||||
field_recon_lock_nb_match.set_value( _recon_lock_nb_match );
|
||||
field_recon_match_mode.set_by_value( _recon_match_mode );
|
||||
};
|
||||
|
||||
void ReconSetupViewMore::focus() {
|
||||
checkbox_load_freqs.focus();
|
||||
}
|
||||
void ReconSetupViewMore::focus() {
|
||||
checkbox_load_freqs.focus();
|
||||
}
|
||||
|
||||
void ReconSetupView::focus() {
|
||||
viewMain.focus();
|
||||
}
|
||||
void ReconSetupView::focus() {
|
||||
viewMain.focus();
|
||||
}
|
||||
|
||||
ReconSetupView::ReconSetupView(
|
||||
NavigationView& nav , std::string _input_file , std::string _output_file , uint32_t _recon_lock_duration , uint32_t _recon_lock_nb_match , uint32_t _recon_match_mode ) : nav_ { nav } , input_file { _input_file } , output_file { _output_file } , recon_lock_duration { _recon_lock_duration } , recon_lock_nb_match { _recon_lock_nb_match } , recon_match_mode { _recon_match_mode }
|
||||
{
|
||||
add_children({
|
||||
&tab_view,
|
||||
&viewMain,
|
||||
&viewMore,
|
||||
&button_save
|
||||
});
|
||||
ReconSetupView::ReconSetupView(
|
||||
NavigationView& nav , std::string _input_file , std::string _output_file , uint32_t _recon_lock_duration , uint32_t _recon_lock_nb_match , uint32_t _recon_match_mode ) : nav_ { nav } , input_file { _input_file } , output_file { _output_file } , recon_lock_duration { _recon_lock_duration } , recon_lock_nb_match { _recon_lock_nb_match } , recon_match_mode { _recon_match_mode }
|
||||
{
|
||||
add_children({
|
||||
&tab_view,
|
||||
&viewMain,
|
||||
&viewMore,
|
||||
&button_save
|
||||
});
|
||||
|
||||
button_save.on_select = [this,&nav](Button&) {
|
||||
viewMain.Save( input_file , output_file );
|
||||
viewMore.Save( recon_lock_duration , recon_lock_nb_match , recon_match_mode );
|
||||
std::vector<std::string> messages ;
|
||||
messages.push_back( input_file );
|
||||
messages.push_back( output_file );
|
||||
messages.push_back( to_string_dec_uint( recon_lock_duration ) );
|
||||
messages.push_back( to_string_dec_uint( recon_lock_nb_match ) );
|
||||
messages.push_back( to_string_dec_uint( recon_match_mode ) );
|
||||
on_changed( messages );
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
button_save.on_select = [this,&nav](Button&) {
|
||||
viewMain.Save( input_file , output_file );
|
||||
viewMore.Save( recon_lock_duration , recon_lock_nb_match , recon_match_mode );
|
||||
std::vector<std::string> messages ;
|
||||
messages.push_back( input_file );
|
||||
messages.push_back( output_file );
|
||||
messages.push_back( to_string_dec_uint( recon_lock_duration ) );
|
||||
messages.push_back( to_string_dec_uint( recon_lock_nb_match ) );
|
||||
messages.push_back( to_string_dec_uint( recon_match_mode ) );
|
||||
on_changed( messages );
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -29,166 +29,166 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume );
|
||||
bool ReconSetupSaveStrings( std::string dest, const std::string input_file , const std::string output_file , const uint32_t recon_lock_duration , const uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume );
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume );
|
||||
bool ReconSetupSaveStrings( std::string dest, const std::string input_file , const std::string output_file , const uint32_t recon_lock_duration , const uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume );
|
||||
|
||||
class ReconSetupViewMain : public View {
|
||||
public:
|
||||
ReconSetupViewMain( NavigationView& nav, Rect parent_rect , std::string input_file , std::string output_file );
|
||||
void Save( std::string &input_file , std::string &output_file );
|
||||
void focus() override;
|
||||
class ReconSetupViewMain : public View {
|
||||
public:
|
||||
ReconSetupViewMain( NavigationView& nav, Rect parent_rect , std::string input_file , std::string output_file );
|
||||
void Save( std::string &input_file , std::string &output_file );
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::string _input_file = { "RECON" };
|
||||
std::string _output_file = { "RECON_RESULTS" };
|
||||
private:
|
||||
std::string _input_file = { "RECON" };
|
||||
std::string _output_file = { "RECON_RESULTS" };
|
||||
|
||||
Button button_load_freqs {
|
||||
{ 1 * 8 , 12 , 18 * 8 , 22 },
|
||||
"select input file"
|
||||
};
|
||||
Text text_input_file {
|
||||
{ 1 * 8 , 4 + 2 * 16, 18 * 8, 22 },
|
||||
"RECON"
|
||||
};
|
||||
Button button_load_freqs {
|
||||
{ 1 * 8 , 12 , 18 * 8 , 22 },
|
||||
"select input file"
|
||||
};
|
||||
Text text_input_file {
|
||||
{ 1 * 8 , 4 + 2 * 16, 18 * 8, 22 },
|
||||
"RECON"
|
||||
};
|
||||
|
||||
Button button_save_freqs {
|
||||
{ 1 * 8 , 4 * 16 - 8 , 18 * 8 , 22 },
|
||||
"select output file"
|
||||
};
|
||||
Button button_output_file {
|
||||
{ 1 * 8 , 5 * 16 - 2, 18 * 8, 22 },
|
||||
"RECON_RESULTS"
|
||||
};
|
||||
Button button_save_freqs {
|
||||
{ 1 * 8 , 4 * 16 - 8 , 18 * 8 , 22 },
|
||||
"select output file"
|
||||
};
|
||||
Button button_output_file {
|
||||
{ 1 * 8 , 5 * 16 - 2, 18 * 8, 22 },
|
||||
"RECON_RESULTS"
|
||||
};
|
||||
|
||||
Checkbox checkbox_autosave_freqs {
|
||||
{ 1 * 8, 7 * 16 - 4 },
|
||||
3,
|
||||
"autosave freqs"
|
||||
};
|
||||
Checkbox checkbox_autosave_freqs {
|
||||
{ 1 * 8, 7 * 16 - 4 },
|
||||
3,
|
||||
"autosave freqs"
|
||||
};
|
||||
|
||||
Checkbox checkbox_autostart_recon {
|
||||
{ 1 * 8, 9 * 16 - 4 },
|
||||
3,
|
||||
"autostart recon"
|
||||
};
|
||||
Checkbox checkbox_autostart_recon {
|
||||
{ 1 * 8, 9 * 16 - 4 },
|
||||
3,
|
||||
"autostart recon"
|
||||
};
|
||||
|
||||
Checkbox checkbox_continuous {
|
||||
{ 1 * 8, 11 * 16 - 4 },
|
||||
3,
|
||||
"continuous"
|
||||
};
|
||||
Checkbox checkbox_clear_output {
|
||||
{ 1 * 8, 13 * 16 - 4 },
|
||||
3,
|
||||
"clear output at start"
|
||||
};
|
||||
};
|
||||
Checkbox checkbox_continuous {
|
||||
{ 1 * 8, 11 * 16 - 4 },
|
||||
3,
|
||||
"continuous"
|
||||
};
|
||||
Checkbox checkbox_clear_output {
|
||||
{ 1 * 8, 13 * 16 - 4 },
|
||||
3,
|
||||
"clear output at start"
|
||||
};
|
||||
};
|
||||
|
||||
class ReconSetupViewMore : public View {
|
||||
public:
|
||||
ReconSetupViewMore( NavigationView& nav, Rect parent_rect , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
class ReconSetupViewMore : public View {
|
||||
public:
|
||||
ReconSetupViewMore( NavigationView& nav, Rect parent_rect , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
|
||||
void Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode );
|
||||
void Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode );
|
||||
|
||||
void focus() override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
private:
|
||||
|
||||
const uint32_t _recon_lock_duration = 50 ;
|
||||
const uint32_t _recon_lock_nb_match = 10 ;
|
||||
const uint32_t _recon_match_mode = 0 ;
|
||||
const uint32_t _recon_lock_duration = 50 ;
|
||||
const uint32_t _recon_lock_nb_match = 10 ;
|
||||
const uint32_t _recon_match_mode = 0 ;
|
||||
|
||||
Checkbox checkbox_load_freqs {
|
||||
{ 1 * 8, 12 },
|
||||
3,
|
||||
"input: load freqs"
|
||||
};
|
||||
Checkbox checkbox_load_freqs {
|
||||
{ 1 * 8, 12 },
|
||||
3,
|
||||
"input: load freqs"
|
||||
};
|
||||
|
||||
Checkbox checkbox_load_ranges {
|
||||
{ 1 * 8, 42 },
|
||||
3,
|
||||
"input: load ranges"
|
||||
};
|
||||
Checkbox checkbox_load_ranges {
|
||||
{ 1 * 8, 42 },
|
||||
3,
|
||||
"input: load ranges"
|
||||
};
|
||||
|
||||
Checkbox checkbox_load_hamradios {
|
||||
{ 1 * 8, 72 },
|
||||
3,
|
||||
"input: load hamradios"
|
||||
};
|
||||
Checkbox checkbox_load_hamradios {
|
||||
{ 1 * 8, 72 },
|
||||
3,
|
||||
"input: load hamradios"
|
||||
};
|
||||
|
||||
Checkbox checkbox_update_ranges_when_recon {
|
||||
{ 1 * 8, 102 },
|
||||
3,
|
||||
"auto update m-ranges"
|
||||
};
|
||||
Text text_recon_lock_duration {
|
||||
{ 1 * 8 , 132 , 22 * 8 , 22 },
|
||||
" ms (lock duration)"
|
||||
};
|
||||
NumberField field_recon_lock_duration {
|
||||
{ 1 * 8, 132 }, // position X , Y
|
||||
4, // number of displayed digits (even empty)
|
||||
{ 50 , 990 }, // range of number
|
||||
10, // rotary encoder increment
|
||||
' ', // filling character
|
||||
false // can loop
|
||||
};
|
||||
Text text_recon_lock_nb {
|
||||
{ 1 * 8 , 162 , 25 * 8 , 22 },
|
||||
" x (nb lock to match freq)"
|
||||
};
|
||||
NumberField field_recon_lock_nb_match {
|
||||
{ 1 * 8, 162 },
|
||||
4,
|
||||
{ 1, 99 },
|
||||
1,
|
||||
' ',
|
||||
false
|
||||
};
|
||||
OptionsField field_recon_match_mode {
|
||||
{ 1 * 8, 192 },
|
||||
20, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
|
||||
{
|
||||
{ "SQL MATCH: CONTINOUS" , 0 },
|
||||
{ "SQL MATCH: SPARSE" , 1 }
|
||||
}
|
||||
};
|
||||
};
|
||||
Checkbox checkbox_update_ranges_when_recon {
|
||||
{ 1 * 8, 102 },
|
||||
3,
|
||||
"auto update m-ranges"
|
||||
};
|
||||
Text text_recon_lock_duration {
|
||||
{ 1 * 8 , 132 , 22 * 8 , 22 },
|
||||
" ms (lock duration)"
|
||||
};
|
||||
NumberField field_recon_lock_duration {
|
||||
{ 1 * 8, 132 }, // position X , Y
|
||||
4, // number of displayed digits (even empty)
|
||||
{ 50 , 990 }, // range of number
|
||||
10, // rotary encoder increment
|
||||
' ', // filling character
|
||||
false // can loop
|
||||
};
|
||||
Text text_recon_lock_nb {
|
||||
{ 1 * 8 , 162 , 25 * 8 , 22 },
|
||||
" x (nb lock to match freq)"
|
||||
};
|
||||
NumberField field_recon_lock_nb_match {
|
||||
{ 1 * 8, 162 },
|
||||
4,
|
||||
{ 1, 99 },
|
||||
1,
|
||||
' ',
|
||||
false
|
||||
};
|
||||
OptionsField field_recon_match_mode {
|
||||
{ 1 * 8, 192 },
|
||||
20, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
|
||||
{
|
||||
{ "SQL MATCH: CONTINOUS" , 0 },
|
||||
{ "SQL MATCH: SPARSE" , 1 }
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class ReconSetupView : public View {
|
||||
public:
|
||||
ReconSetupView( NavigationView& nav , std::string _input_file , std::string _output_file , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
class ReconSetupView : public View {
|
||||
public:
|
||||
ReconSetupView( NavigationView& nav , std::string _input_file , std::string _output_file , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
|
||||
std::function<void( std::vector<std::string> messages )> on_changed { };
|
||||
std::function<void( std::vector<std::string> messages )> on_changed { };
|
||||
|
||||
void focus() override;
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Recon setup"; };
|
||||
std::string title() const override { return "Recon setup"; };
|
||||
|
||||
private:
|
||||
private:
|
||||
|
||||
NavigationView& nav_ ;
|
||||
NavigationView& nav_ ;
|
||||
|
||||
std::string input_file = { "RECON" };
|
||||
std::string output_file = { "RECON_RESULTS" };
|
||||
uint32_t recon_lock_duration = 50 ;
|
||||
uint32_t recon_lock_nb_match = 10 ;
|
||||
uint32_t recon_match_mode = 0 ;
|
||||
std::string input_file = { "RECON" };
|
||||
std::string output_file = { "RECON_RESULTS" };
|
||||
uint32_t recon_lock_duration = 50 ;
|
||||
uint32_t recon_lock_nb_match = 10 ;
|
||||
uint32_t recon_match_mode = 0 ;
|
||||
|
||||
Rect view_rect = { 0, 3 * 8, 240, 230 };
|
||||
Rect view_rect = { 0, 3 * 8, 240, 230 };
|
||||
|
||||
ReconSetupViewMain viewMain{ nav_ , view_rect , input_file , output_file };
|
||||
ReconSetupViewMore viewMore{ nav_ , view_rect , recon_lock_duration , recon_lock_nb_match , recon_match_mode };
|
||||
ReconSetupViewMain viewMain{ nav_ , view_rect , input_file , output_file };
|
||||
ReconSetupViewMore viewMore{ nav_ , view_rect , recon_lock_duration , recon_lock_nb_match , recon_match_mode };
|
||||
|
||||
TabView tab_view {
|
||||
{ "Main", Color::cyan() , &viewMain },
|
||||
{ "More", Color::green(), &viewMore }
|
||||
};
|
||||
Button button_save {
|
||||
{ 9 * 8, 255, 14 * 8 , 40 },
|
||||
"SAVE"
|
||||
};
|
||||
};
|
||||
TabView tab_view {
|
||||
{ "Main", Color::cyan() , &viewMain },
|
||||
{ "More", Color::green(), &viewMore }
|
||||
};
|
||||
Button button_save {
|
||||
{ 9 * 8, 255, 14 * 8 , 40 },
|
||||
"SAVE"
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -88,8 +88,6 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq(rf::Frequency f);
|
||||
|
||||
const Style style_grey { // scanning
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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_sd_over_usb.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SdOverUsbView::SdOverUsbView(NavigationView& nav) : nav_ (nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&button_run
|
||||
});
|
||||
|
||||
button_run.on_select = [this](Button&) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{ 0, 0, portapack::display.width(), portapack::display.height() },
|
||||
ui::Color::black()
|
||||
);
|
||||
|
||||
painter.draw_bitmap(
|
||||
{ portapack::display.width()/2-8, portapack::display.height()/2-8 },
|
||||
bitmap_icon_hackrf,
|
||||
ui::Color::yellow(),
|
||||
ui::Color::black()
|
||||
);
|
||||
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
|
||||
portapack::shutdown(true);
|
||||
m4_init(portapack::spi_flash::image_tag_usb_sd, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
/* will not return*/
|
||||
};
|
||||
}
|
||||
|
||||
void SdOverUsbView::focus() {
|
||||
button_run.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __UI_SD_OVER_USB_H__
|
||||
#define __UI_SD_OVER_USB_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ff.h"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SdOverUsbView : public View {
|
||||
public:
|
||||
SdOverUsbView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Labels labels {
|
||||
{ { 3 * 8, 2 * 16 }, "Click Run to start the", Color::white() },
|
||||
{ { 3 * 8, 3 * 16 }, "USB Mass Storage Mode.", Color::white() },
|
||||
{ { 3 * 8, 5 * 16 }, "It can take up to 20s", Color::white() },
|
||||
{ { 3 * 8, 6 * 16 }, "for the drive to be", Color::white() },
|
||||
{ { 3 * 8, 7 * 16 }, "available.", Color::white() },
|
||||
};
|
||||
|
||||
Button button_run {
|
||||
{ 9 * 8, 15 * 16, 12 * 8, 3 * 16 },
|
||||
"Run"
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_SD_OVER_USB_H__*/
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
~WipeSDView();
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Wipe SD Card"; };
|
||||
std::string title() const override { return "Wipe sdcard"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -38,353 +38,542 @@ using namespace portapack;
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
button_save.on_select = [&nav, this](Button&){
|
||||
const auto model = this->form_collect();
|
||||
const rtc::RTC new_datetime {
|
||||
model.year, model.month, model.day,
|
||||
model.hour, model.minute, model.second
|
||||
};
|
||||
rtcSetTime(&RTCD1, &new_datetime);
|
||||
nav.pop();
|
||||
},
|
||||
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
button_save.on_select = [&nav, this](Button&){
|
||||
const auto model = this->form_collect();
|
||||
const rtc::RTC new_datetime {
|
||||
model.year, model.month, model.day,
|
||||
model.hour, model.minute, model.second
|
||||
button_cancel.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
},
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_year,
|
||||
&field_month,
|
||||
&field_day,
|
||||
&field_hour,
|
||||
&field_minute,
|
||||
&field_second,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
SetDateTimeModel model {
|
||||
datetime.year(),
|
||||
datetime.month(),
|
||||
datetime.day(),
|
||||
datetime.hour(),
|
||||
datetime.minute(),
|
||||
datetime.second()
|
||||
};
|
||||
rtcSetTime(&RTCD1, &new_datetime);
|
||||
nav.pop();
|
||||
},
|
||||
|
||||
button_cancel.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
},
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_year,
|
||||
&field_month,
|
||||
&field_day,
|
||||
&field_hour,
|
||||
&field_minute,
|
||||
&field_second,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
SetDateTimeModel model {
|
||||
datetime.year(),
|
||||
datetime.month(),
|
||||
datetime.day(),
|
||||
datetime.hour(),
|
||||
datetime.minute(),
|
||||
datetime.second()
|
||||
};
|
||||
|
||||
form_init(model);
|
||||
}
|
||||
|
||||
void SetDateTimeView::focus() {
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
void SetDateTimeView::form_init(const SetDateTimeModel& model) {
|
||||
field_year.set_value(model.year);
|
||||
field_month.set_value(model.month);
|
||||
field_day.set_value(model.day);
|
||||
field_hour.set_value(model.hour);
|
||||
field_minute.set_value(model.minute);
|
||||
field_second.set_value(model.second);
|
||||
}
|
||||
|
||||
SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value())
|
||||
};
|
||||
}
|
||||
|
||||
SetRadioView::SetRadioView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
button_cancel.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
const auto reference = portapack::clock_manager.get_reference();
|
||||
|
||||
std::string source_name("---");
|
||||
switch(reference.source) {
|
||||
case ClockManager::ReferenceSource::Xtal: source_name = "HackRF"; break;
|
||||
case ClockManager::ReferenceSource::PortaPack: source_name = "PortaPack"; break;
|
||||
case ClockManager::ReferenceSource::External: source_name = "External"; break;
|
||||
form_init(model);
|
||||
}
|
||||
|
||||
value_source.set(source_name);
|
||||
value_source_frequency.set(to_string_dec_uint(reference.frequency / 1000000, 2) + "." + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
|
||||
void SetDateTimeView::focus() {
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
label_source.set_style(&style_text);
|
||||
value_source.set_style(&style_text);
|
||||
value_source_frequency.set_style(&style_text);
|
||||
void SetDateTimeView::form_init(const SetDateTimeModel& model) {
|
||||
field_year.set_value(model.year);
|
||||
field_month.set_value(model.month);
|
||||
field_day.set_value(model.day);
|
||||
field_hour.set_value(model.hour);
|
||||
field_minute.set_value(model.minute);
|
||||
field_second.set_value(model.second);
|
||||
}
|
||||
|
||||
add_children({
|
||||
&label_source,
|
||||
&value_source,
|
||||
&value_source_frequency,
|
||||
});
|
||||
SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value())
|
||||
};
|
||||
}
|
||||
|
||||
SetRadioView::SetRadioView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
button_cancel.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
const auto reference = portapack::clock_manager.get_reference();
|
||||
|
||||
std::string source_name("---");
|
||||
switch(reference.source) {
|
||||
case ClockManager::ReferenceSource::Xtal: source_name = "HackRF"; break;
|
||||
case ClockManager::ReferenceSource::PortaPack: source_name = "PortaPack"; break;
|
||||
case ClockManager::ReferenceSource::External: source_name = "External"; break;
|
||||
}
|
||||
|
||||
value_source.set(source_name);
|
||||
value_source_frequency.set(to_string_dec_uint(reference.frequency / 1000000, 2) + "." + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
|
||||
|
||||
label_source.set_style(&style_text);
|
||||
value_source.set_style(&style_text);
|
||||
value_source_frequency.set_style(&style_text);
|
||||
|
||||
if( reference.source == ClockManager::ReferenceSource::Xtal ) {
|
||||
add_children({
|
||||
&labels_correction,
|
||||
&field_ppm,
|
||||
});
|
||||
&label_source,
|
||||
&value_source,
|
||||
&value_source_frequency,
|
||||
});
|
||||
|
||||
if( reference.source == ClockManager::ReferenceSource::Xtal ) {
|
||||
add_children({
|
||||
&labels_correction,
|
||||
&field_ppm,
|
||||
});
|
||||
}
|
||||
|
||||
add_children({
|
||||
&check_clkout,
|
||||
&field_clkout_freq,
|
||||
&labels_clkout_khz,
|
||||
&value_freq_step,
|
||||
&labels_bias,
|
||||
&check_bias,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
SetFrequencyCorrectionModel model {
|
||||
static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000) , 0
|
||||
};
|
||||
|
||||
form_init(model);
|
||||
|
||||
check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
|
||||
check_clkout.on_select = [this](Checkbox&, bool v) {
|
||||
clock_manager.enable_clock_output(v);
|
||||
portapack::persistent_memory::set_clkout_enabled(v);
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
|
||||
value_freq_step.set_style(&style_text);
|
||||
|
||||
field_clkout_freq.on_select = [this](NumberField&) {
|
||||
freq_step_khz++;
|
||||
if(freq_step_khz > 3) {
|
||||
freq_step_khz = 0;
|
||||
}
|
||||
switch(freq_step_khz) {
|
||||
case 0:
|
||||
value_freq_step.set(" |");
|
||||
break;
|
||||
case 1:
|
||||
value_freq_step.set(" | ");
|
||||
break;
|
||||
case 2:
|
||||
value_freq_step.set(" | ");
|
||||
break;
|
||||
case 3:
|
||||
value_freq_step.set("| ");
|
||||
break;
|
||||
}
|
||||
field_clkout_freq.set_step(pow(10, freq_step_khz));
|
||||
};
|
||||
|
||||
check_bias.set_value(portapack::get_antenna_bias());
|
||||
check_bias.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::set_antenna_bias(v);
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&){
|
||||
const auto model = this->form_collect();
|
||||
portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
|
||||
portapack::persistent_memory::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
add_children({
|
||||
&check_clkout,
|
||||
&field_clkout_freq,
|
||||
&labels_clkout_khz,
|
||||
&value_freq_step,
|
||||
&labels_bias,
|
||||
&check_bias,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
void SetRadioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
SetFrequencyCorrectionModel model {
|
||||
static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000) , 0
|
||||
};
|
||||
void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
|
||||
field_ppm.set_value(model.ppm);
|
||||
}
|
||||
|
||||
form_init(model);
|
||||
|
||||
check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
|
||||
check_clkout.on_select = [this](Checkbox&, bool v) {
|
||||
clock_manager.enable_clock_output(v);
|
||||
portapack::persistent_memory::set_clkout_enabled(v);
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
|
||||
value_freq_step.set_style(&style_text);
|
||||
|
||||
field_clkout_freq.on_select = [this](NumberField&) {
|
||||
freq_step_khz++;
|
||||
if(freq_step_khz > 3) {
|
||||
freq_step_khz = 0;
|
||||
}
|
||||
switch(freq_step_khz) {
|
||||
case 0:
|
||||
value_freq_step.set(" |");
|
||||
break;
|
||||
case 1:
|
||||
value_freq_step.set(" | ");
|
||||
break;
|
||||
case 2:
|
||||
value_freq_step.set(" | ");
|
||||
break;
|
||||
case 3:
|
||||
value_freq_step.set("| ");
|
||||
break;
|
||||
}
|
||||
field_clkout_freq.set_step(pow(10, freq_step_khz));
|
||||
};
|
||||
|
||||
check_bias.set_value(portapack::get_antenna_bias());
|
||||
check_bias.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::set_antenna_bias(v);
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&){
|
||||
const auto model = this->form_collect();
|
||||
portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
|
||||
portapack::persistent_memory::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetRadioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
|
||||
field_ppm.set_value(model.ppm);
|
||||
}
|
||||
|
||||
SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
return {
|
||||
.ppm = static_cast<int8_t>(field_ppm.value()),
|
||||
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
SetUIView::SetUIView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_disable_touchscreen,
|
||||
&checkbox_speaker,
|
||||
&checkbox_bloff,
|
||||
&options_bloff,
|
||||
&checkbox_showsplash,
|
||||
&checkbox_showclock,
|
||||
&options_clockformat,
|
||||
&checkbox_guireturnflag,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
|
||||
checkbox_speaker.set_value(persistent_memory::config_speaker());
|
||||
checkbox_showsplash.set_value(persistent_memory::config_splash());
|
||||
checkbox_showclock.set_value(!persistent_memory::hide_clock());
|
||||
checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
|
||||
|
||||
const auto backlight_config = persistent_memory::config_backlight_timer();
|
||||
checkbox_bloff.set_value(backlight_config.timeout_enabled());
|
||||
options_bloff.set_by_value(backlight_config.timeout_enum());
|
||||
|
||||
if (persistent_memory::clock_with_date()) {
|
||||
options_clockformat.set_selected_index(1);
|
||||
} else {
|
||||
options_clockformat.set_selected_index(0);
|
||||
SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
return {
|
||||
.ppm = static_cast<int8_t>(field_ppm.value()),
|
||||
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_config_backlight_timer({
|
||||
(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()
|
||||
});
|
||||
|
||||
if (checkbox_showclock.value()){
|
||||
if (options_clockformat.selected_index() == 1)
|
||||
persistent_memory::set_clock_with_date(true);
|
||||
else
|
||||
persistent_memory::set_clock_with_date(false);
|
||||
SetUIView::SetUIView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_disable_touchscreen,
|
||||
&checkbox_speaker,
|
||||
&checkbox_bloff,
|
||||
&options_bloff,
|
||||
&checkbox_showsplash,
|
||||
&checkbox_showclock,
|
||||
&options_clockformat,
|
||||
&checkbox_guireturnflag,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
|
||||
checkbox_speaker.set_value(persistent_memory::config_speaker());
|
||||
checkbox_showsplash.set_value(persistent_memory::config_splash());
|
||||
checkbox_showclock.set_value(!persistent_memory::hide_clock());
|
||||
checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
|
||||
|
||||
const auto backlight_config = persistent_memory::config_backlight_timer();
|
||||
checkbox_bloff.set_value(backlight_config.timeout_enabled());
|
||||
options_bloff.set_by_value(backlight_config.timeout_enum());
|
||||
|
||||
if (persistent_memory::clock_with_date()) {
|
||||
options_clockformat.set_selected_index(1);
|
||||
} else {
|
||||
options_clockformat.set_selected_index(0);
|
||||
}
|
||||
|
||||
if (checkbox_speaker.value()) audio::output::speaker_mute(); //Just mute audio if speaker is disabled
|
||||
persistent_memory::set_config_speaker(checkbox_speaker.value()); //Store Speaker status
|
||||
StatusRefreshMessage message { }; //Refresh status bar with/out speaker
|
||||
EventDispatcher::send_message(message);
|
||||
|
||||
persistent_memory::set_config_splash(checkbox_showsplash.value());
|
||||
persistent_memory::set_clock_hidden(!checkbox_showclock.value());
|
||||
persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetUIView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_config_backlight_timer({
|
||||
(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()
|
||||
});
|
||||
|
||||
if (checkbox_showclock.value()){
|
||||
if (options_clockformat.selected_index() == 1)
|
||||
persistent_memory::set_clock_with_date(true);
|
||||
else
|
||||
persistent_memory::set_clock_with_date(false);
|
||||
}
|
||||
|
||||
if (checkbox_speaker.value()) audio::output::speaker_mute(); //Just mute audio if speaker is disabled
|
||||
persistent_memory::set_config_speaker(checkbox_speaker.value()); //Store Speaker status
|
||||
StatusRefreshMessage message { }; //Refresh status bar with/out speaker
|
||||
EventDispatcher::send_message(message);
|
||||
|
||||
persistent_memory::set_config_splash(checkbox_showsplash.value());
|
||||
persistent_memory::set_clock_hidden(!checkbox_showclock.value());
|
||||
persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetUIView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Appl. Settings
|
||||
// ---------------------------------------------------------
|
||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_load_app_settings,
|
||||
&checkbox_save_app_settings,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
// ---------------------------------------------------------
|
||||
// Appl. Settings
|
||||
// ---------------------------------------------------------
|
||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_load_app_settings,
|
||||
&checkbox_save_app_settings,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetAppSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
void SetAppSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&field_tone_mix,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
// ---------------------------------------------------------
|
||||
// Converter Settings
|
||||
// ---------------------------------------------------------
|
||||
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&check_show_converter,
|
||||
&check_converter,
|
||||
&converter_mode,
|
||||
&button_converter_freq,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
field_tone_mix.set_value(persistent_memory::tone_mix());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_tone_mix(field_tone_mix.value());
|
||||
nav.pop();
|
||||
};
|
||||
check_show_converter.set_value(!portapack::persistent_memory::config_hide_converter());
|
||||
check_show_converter.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::persistent_memory::set_config_hide_converter(!v);
|
||||
if( !v )
|
||||
{
|
||||
check_converter.set_value(false);
|
||||
}
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() );
|
||||
//Refresh status bar with/out converter
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
check_converter.set_value(portapack::persistent_memory::config_converter());
|
||||
check_converter.on_select = [this](Checkbox&, bool v) {
|
||||
if( v )
|
||||
{
|
||||
check_show_converter.set_value(true);
|
||||
portapack::persistent_memory::set_config_hide_converter(false);
|
||||
}
|
||||
portapack::persistent_memory::set_config_converter(v);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() );
|
||||
//Refresh status bar with/out converter
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
void SetAudioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
converter_mode.set_by_value( portapack::persistent_memory::config_updown_converter() );
|
||||
converter_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_config_updown_converter( v );
|
||||
//Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
|
||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_bigger_qr,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
button_converter_freq.set_text( to_string_short_freq( portapack::persistent_memory::config_converter_freq() ) + "MHz");
|
||||
button_converter_freq.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq() );
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
portapack::persistent_memory::set_config_converter_freq( f );
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() );
|
||||
button_converter_freq.set_text( "<" + to_string_short_freq( f ) + " MHz>" );
|
||||
};
|
||||
};
|
||||
|
||||
checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
void SetConverterSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
// Persistent Memory Settings
|
||||
// ---------------------------------------------------------
|
||||
SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_pmem_about,
|
||||
&text_pmem_informations,
|
||||
&text_pmem_status,
|
||||
&check_load_mem_at_startup,
|
||||
&button_save_mem_to_file,
|
||||
&button_load_mem_from_file,
|
||||
&button_load_mem_defaults,
|
||||
&button_return
|
||||
});
|
||||
|
||||
void SetQRCodeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
bool load_mem_at_startup = false ;
|
||||
File pmem_flag_file_handle ;
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Settings main menu
|
||||
// ---------------------------------------------------------
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous, [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items({
|
||||
{ "Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav](){ nav.push<SetAudioView>(); } },
|
||||
{ "Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav](){ nav.push<SetRadioView>(); } },
|
||||
{ "User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav](){ nav.push<SetUIView>(); } },
|
||||
{ "Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav](){ nav.push<SetDateTimeView>(); } },
|
||||
{ "Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav](){ nav.push<TouchCalibrationView>(); } },
|
||||
{ "App Settings", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav](){ nav.push<SetAppSettingsView>(); } },
|
||||
{ "QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav](){ nav.push<SetQRCodeView>(); } }
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
std::string folder = "SETTINGS";
|
||||
make_new_directory(folder);
|
||||
|
||||
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG" ;
|
||||
auto result = pmem_flag_file_handle.open(pmem_flag_file);
|
||||
if(!result.is_valid())
|
||||
{
|
||||
load_mem_at_startup = true ;
|
||||
}
|
||||
check_load_mem_at_startup.set_value(load_mem_at_startup);
|
||||
check_load_mem_at_startup.on_select = [this](Checkbox&, bool v) {
|
||||
File pmem_flag_file_handle ;
|
||||
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG" ;
|
||||
if( v )
|
||||
{
|
||||
auto result = pmem_flag_file_handle.open(pmem_flag_file);
|
||||
if(result.is_valid())
|
||||
{
|
||||
auto result = pmem_flag_file_handle.create(pmem_flag_file); //third: create if it is not there
|
||||
if( !result.is_valid() )
|
||||
{
|
||||
text_pmem_status.set("pmem flag file created");
|
||||
}
|
||||
else
|
||||
{
|
||||
text_pmem_status.set("!err. creating pmem flagfile!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text_pmem_status.set("pmem flag already present");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = delete_file( pmem_flag_file );
|
||||
if( result != 0 )
|
||||
{
|
||||
text_pmem_status.set("!err. deleting pmem flagfile!");
|
||||
}
|
||||
else
|
||||
{
|
||||
text_pmem_status.set("pmem flag file deleted");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
button_save_mem_to_file.on_select = [&nav, this](Button&) {
|
||||
if( !portapack::persistent_memory::save_persistent_settings_to_file("SETTINGS/pmem_settings") )
|
||||
{
|
||||
text_pmem_status.set("!problem saving settings!");
|
||||
}
|
||||
else
|
||||
{
|
||||
text_pmem_status.set("settings saved");
|
||||
}
|
||||
};
|
||||
|
||||
button_load_mem_from_file.on_select = [&nav, this](Button&) {
|
||||
if( !portapack::persistent_memory::load_persistent_settings_from_file("SETTINGS/pmem_settings") )
|
||||
{
|
||||
text_pmem_status.set("!problem loading settings!");
|
||||
}
|
||||
else
|
||||
{
|
||||
text_pmem_status.set("settings loaded");
|
||||
//Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
};
|
||||
|
||||
button_load_mem_defaults.on_select = [&nav, this](Button&) {
|
||||
nav.push<ModalMessageView>(
|
||||
"Warning!",
|
||||
"This will reset the p.mem\nand set the default settings",
|
||||
YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
button_return.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetPersistentMemoryView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
//
|
||||
// Audio settings
|
||||
//
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&field_tone_mix,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
field_tone_mix.set_value(persistent_memory::tone_mix());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_tone_mix(field_tone_mix.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetAudioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
|
||||
add_children({
|
||||
&checkbox_bigger_qr,
|
||||
&button_save,
|
||||
&button_cancel
|
||||
});
|
||||
|
||||
checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void SetQRCodeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Settings main menu
|
||||
// ---------------------------------------------------------
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous, [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items({
|
||||
{ "Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav](){ nav.push<SetAudioView>(); } },
|
||||
{ "Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav](){ nav.push<SetRadioView>(); } },
|
||||
{ "User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav](){ nav.push<SetUIView>(); } },
|
||||
{ "Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav](){ nav.push<SetDateTimeView>(); } },
|
||||
{ "Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav](){ nav.push<TouchCalibrationView>(); } },
|
||||
{ "App Settings", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav](){ nav.push<SetAppSettingsView>(); } },
|
||||
{ "Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav](){ nav.push<SetConverterSettingsView>(); } },
|
||||
{ "QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav](){ nav.push<SetQRCodeView>(); } },
|
||||
{ "P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav](){ nav.push<SetPersistentMemoryView>(); } },
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Radio settings"; };
|
||||
std::string title() const override { return "Radio"; };
|
||||
|
||||
private:
|
||||
const Style style_text {
|
||||
@@ -154,7 +154,7 @@ private:
|
||||
{ { 6 * 8, 4 * 16 }, "PPM", Color::light_grey() },
|
||||
};
|
||||
|
||||
Checkbox check_clkout {
|
||||
Checkbox check_clkout {
|
||||
{ 18, (6 * 16 - 4) },
|
||||
13,
|
||||
"Enable CLKOUT"
|
||||
@@ -193,7 +193,7 @@ private:
|
||||
};
|
||||
|
||||
Checkbox check_bias {
|
||||
{ 28, 13 * 16 },
|
||||
{ 18, 12 * 16 },
|
||||
5,
|
||||
"Turn on bias voltage"
|
||||
};
|
||||
@@ -219,7 +219,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "UI settings"; };
|
||||
std::string title() const override { return "UI"; };
|
||||
|
||||
private:
|
||||
|
||||
@@ -299,7 +299,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "App Settings"; };
|
||||
std::string title() const override { return "AppSettings"; };
|
||||
|
||||
private:
|
||||
|
||||
@@ -314,6 +314,53 @@ private:
|
||||
25,
|
||||
"Save app settings"
|
||||
};
|
||||
|
||||
Button button_save {
|
||||
{ 2 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Save"
|
||||
};
|
||||
|
||||
Button button_cancel {
|
||||
{ 16 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetConverterSettingsView : public View {
|
||||
public:
|
||||
SetConverterSettingsView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Converter"; };
|
||||
|
||||
private:
|
||||
|
||||
Checkbox check_show_converter {
|
||||
{ 18, 4 * 16},
|
||||
19,
|
||||
"show/hide converter"
|
||||
};
|
||||
|
||||
Checkbox check_converter {
|
||||
{ 18, 6 * 16},
|
||||
7,
|
||||
"enable/disable converter"
|
||||
};
|
||||
|
||||
OptionsField converter_mode {
|
||||
{ 18 , 8 * 16 + 4 },
|
||||
0,
|
||||
{
|
||||
{" + ",0}, // up converter
|
||||
{" - ",1} // down converter
|
||||
}
|
||||
};
|
||||
|
||||
Button button_converter_freq {
|
||||
{ 18 + 4 * 8 , 8 * 16 , 16 * 8 , 24 },
|
||||
"",
|
||||
};
|
||||
|
||||
Button button_save {
|
||||
{ 2 * 8, 16 * 16, 12 * 8, 32 },
|
||||
@@ -332,7 +379,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Audio settings"; };
|
||||
std::string title() const override { return "Audio"; };
|
||||
|
||||
private:
|
||||
Labels labels {
|
||||
@@ -385,6 +432,59 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class SetPersistentMemoryView : public View {
|
||||
public:
|
||||
SetPersistentMemoryView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "P.Mem Mgmt"; };
|
||||
|
||||
private:
|
||||
|
||||
Text text_pmem_about {
|
||||
{ 0, 1 * 16, 240 , 16 },
|
||||
"PersistentMemory from/to SD"
|
||||
};
|
||||
|
||||
Text text_pmem_informations {
|
||||
{ 0, 2 * 16, 240 , 16 },
|
||||
"use: when no/dead coin bat."
|
||||
};
|
||||
|
||||
Text text_pmem_status {
|
||||
{ 0, 3 * 16, 240 , 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Checkbox check_load_mem_at_startup {
|
||||
{ 18, 6 * 16},
|
||||
19,
|
||||
"load from sd at startup"
|
||||
};
|
||||
|
||||
Button button_save_mem_to_file {
|
||||
{ 0, 8 * 16, 240, 32 },
|
||||
"save p.mem to sdcard"
|
||||
};
|
||||
|
||||
Button button_load_mem_from_file {
|
||||
{ 0, 10 * 16 + 4 , 240, 32 },
|
||||
"load p.mem from sdcard"
|
||||
};
|
||||
|
||||
Button button_load_mem_defaults {
|
||||
{ 0, 12 * 16 + 8 , 240, 32 },
|
||||
"! reset p.mem, load defaults !"
|
||||
};
|
||||
|
||||
Button button_return {
|
||||
{ 16 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Return",
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -49,26 +49,26 @@ private:
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
const std::string shape_strings[7] = {
|
||||
"CW",
|
||||
"Sine",
|
||||
"Triangle",
|
||||
"Saw up",
|
||||
"Saw down",
|
||||
"Square",
|
||||
"Noise"
|
||||
"CW-just carrier",
|
||||
"Sine signal ",
|
||||
"Triangle signal",
|
||||
"Saw up signal ",
|
||||
"Saw down signal",
|
||||
"Square signal ",
|
||||
"Noise signal " // using 16 bits LFSR register, 16 order polynomial feedback.
|
||||
};
|
||||
|
||||
bool auto_update { false };
|
||||
|
||||
Labels labels {
|
||||
{ { 6 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 7 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 3 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 6 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 22 * 8, 15 * 8 + 4 }, "s.", Color::light_grey() },
|
||||
{ { 8 * 8, 20 * 8 }, "Modulation: FM", Color::light_grey() }
|
||||
};
|
||||
|
||||
ImageOptionsField options_shape {
|
||||
{ 13 * 8, 4, 32, 32 },
|
||||
{ 10 * 8, 4, 32, 32 },
|
||||
Color::white(),
|
||||
Color::black(),
|
||||
{
|
||||
@@ -83,7 +83,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_shape {
|
||||
{ 18 * 8, 4 + 10, 8 * 8, 16 },
|
||||
{ 15 * 8, 4 + 10, 8 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
@@ -94,12 +94,12 @@ private:
|
||||
};
|
||||
|
||||
Button button_update {
|
||||
{ 6 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
{ 5 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
"Update"
|
||||
};
|
||||
|
||||
Checkbox checkbox_auto {
|
||||
{ 16 * 8, 10 * 8 },
|
||||
{ 15 * 8, 10 * 8 },
|
||||
4,
|
||||
"Auto"
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Radiosonde RX"; };
|
||||
std::string title() const override { return "Radiosnd RX"; };
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
|
||||
std::string title() const override { return "SSTV TX (beta)"; };
|
||||
std::string title() const override { return "SSTV TX"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "TouchTunes TX"; };
|
||||
std::string title() const override { return "TouchTunes"; };
|
||||
|
||||
private:
|
||||
uint32_t scan_button_index { };
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace ui
|
||||
public:
|
||||
WhipCalcView(NavigationView &nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Antenna length"; };
|
||||
std::string title() const override { return "Ant. length"; };
|
||||
|
||||
private:
|
||||
const double speed_of_light_mps = 299792458.0; // m/s
|
||||
|
||||
+2277
-2233
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -28,22 +29,20 @@ using namespace lpc43xx;
|
||||
|
||||
#include "message.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "lz4.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
/* TODO: OK, this is cool, but how do I put the M4 to sleep so I can switch to
|
||||
* a different image? Other than asking the old image to sleep while the M0
|
||||
* makes changes?
|
||||
*
|
||||
* I suppose I could force M4MEMMAP to an invalid memory reason which would
|
||||
* cause an exception and effectively halt the M4. But that feels gross.
|
||||
*/
|
||||
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset) {
|
||||
const portapack::spi_flash::chunk_t* chunk = reinterpret_cast<const portapack::spi_flash::chunk_t*>(portapack::spi_flash::images.base());
|
||||
while(chunk->tag) {
|
||||
if( chunk->tag == image_tag ) {
|
||||
/* Initialize M4 code RAM */
|
||||
std::memcpy(reinterpret_cast<void*>(to.base()), &chunk->data[0], chunk->length);
|
||||
if(chunk->tag == image_tag) {
|
||||
|
||||
const void *src = &chunk->data[0];
|
||||
void *dst = reinterpret_cast<void*>(to.base());
|
||||
|
||||
/* extract and initialize M4 code RAM */
|
||||
unlz4_len(src, dst, chunk->compressed_data_size);
|
||||
|
||||
/* M4 core is assumed to be sleeping with interrupts off, so we can mess
|
||||
* with its address space and RAM without concern.
|
||||
|
||||
@@ -43,6 +43,7 @@ using namespace lpc43xx;
|
||||
#include <array>
|
||||
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -262,6 +263,8 @@ void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
void EventDispatcher::handle_lcd_frame_sync() {
|
||||
DisplayFrameSyncMessage message;
|
||||
message_map.send(&message);
|
||||
|
||||
static_cast<ui::SystemView *>(top_widget)->paint_overlay();
|
||||
painter.paint_widget_tree(top_widget);
|
||||
|
||||
portapack::backlight()->on();
|
||||
@@ -304,7 +307,12 @@ void EventDispatcher::handle_switches() {
|
||||
if( switches_state[i] ) {
|
||||
const auto event = static_cast<ui::KeyEvent>(i);
|
||||
if( !event_bubble_key(event) ) {
|
||||
context.focus_manager().update(top_widget, event);
|
||||
if (switches_state[(size_t)ui::KeyEvent::Dfu]) {
|
||||
static_cast<ui::SystemView *>(top_widget)->toggle_overlay();
|
||||
}
|
||||
else {
|
||||
context.focus_manager().update(top_widget, event);
|
||||
}
|
||||
}
|
||||
|
||||
in_key_event = true;
|
||||
|
||||
@@ -250,6 +250,15 @@ std::string filesystem_error::what() const {
|
||||
}
|
||||
}
|
||||
|
||||
path path::parent_path() const {
|
||||
const auto index = _s.find_last_of(preferred_separator);
|
||||
if( index == _s.npos ) {
|
||||
return { }; // NB: Deviation from STL.
|
||||
} else {
|
||||
return _s.substr(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
path path::extension() const {
|
||||
const auto t = filename().native();
|
||||
const auto index = t.find_last_of(u'.');
|
||||
@@ -296,6 +305,14 @@ path& path::replace_extension(const path& replacement) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs) {
|
||||
return lhs.native() == rhs.native();
|
||||
}
|
||||
|
||||
bool operator!=(const path& lhs, const path& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const path& lhs, const path& rhs) {
|
||||
return lhs.native() < rhs.native();
|
||||
}
|
||||
@@ -304,6 +321,18 @@ bool operator>(const path& lhs, const path& rhs) {
|
||||
return lhs.native() > rhs.native();
|
||||
}
|
||||
|
||||
path operator+(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
path operator/(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result /= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild
|
||||
@@ -311,7 +340,7 @@ directory_iterator::directory_iterator(
|
||||
{
|
||||
impl = std::make_shared<Impl>();
|
||||
const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str()));
|
||||
if( result != FR_OK ) {
|
||||
if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
|
||||
impl.reset();
|
||||
// TODO: Throw exception if/when I enable exceptions...
|
||||
}
|
||||
@@ -333,6 +362,20 @@ bool is_regular_file(const file_status s) {
|
||||
return !(s & AM_DIR);
|
||||
}
|
||||
|
||||
bool file_exists(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK;
|
||||
}
|
||||
|
||||
bool is_directory(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK && is_directory(static_cast<file_status>(filinfo.fattrib));
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters { 0 };
|
||||
FATFS* fs;
|
||||
|
||||
@@ -123,6 +123,7 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path parent_path() const;
|
||||
path extension() const;
|
||||
path filename() const;
|
||||
path stem() const;
|
||||
@@ -151,14 +152,25 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator/=(const path& p) {
|
||||
if (_s.back() != preferred_separator)
|
||||
_s += preferred_separator;
|
||||
_s += p._s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& replace_extension(const path& replacement = path());
|
||||
|
||||
private:
|
||||
string_type _s;
|
||||
};
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs);
|
||||
bool operator!=(const path& lhs, const path& rhs);
|
||||
bool operator<(const path& lhs, const path& rhs);
|
||||
bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
@@ -227,6 +239,8 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
|
||||
|
||||
bool is_directory(const file_status s);
|
||||
bool is_regular_file(const file_status s);
|
||||
bool file_exists(const path& file_path);
|
||||
bool is_directory(const path& file_path);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
@@ -245,6 +259,8 @@ uint32_t make_new_directory(const std::filesystem::path& dir_path);
|
||||
|
||||
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
|
||||
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
|
||||
|
||||
/* Gets an auto incrementing filename. */
|
||||
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern);
|
||||
|
||||
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
|
||||
|
||||
@@ -229,7 +229,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
|
||||
{
|
||||
db.push_back({ frequency_a, frequency_b, description, type , modulation , bandwidth , step , tone });
|
||||
n++;
|
||||
if (n >= FREQMAN_MAX_PER_FILE) return true;
|
||||
if (n > FREQMAN_MAX_PER_FILE) return true;
|
||||
}
|
||||
|
||||
line_start = line_end + 1;
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#define FREQMAN_DESC_MAX_LEN 30
|
||||
#define FREQMAN_MAX_PER_FILE 500 /* MAX PER FILES */
|
||||
#define FREQMAN_MAX_PER_FILE_STR "500" /*STRING OF FREQMAN_MAX_PER_FILE */
|
||||
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
|
||||
#define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit
|
||||
// It was tested and lowered to leave a bit of space to the caller
|
||||
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE
|
||||
|
||||
using namespace ui;
|
||||
using namespace std;
|
||||
|
||||
@@ -42,7 +42,7 @@ using namespace hackrf::one;
|
||||
|
||||
static Thread* thread_controls_event = NULL;
|
||||
|
||||
static std::array<Debounce, 7> switch_debounce;
|
||||
static std::array<Debounce, 8> switch_debounce;
|
||||
|
||||
static Encoder encoder;
|
||||
|
||||
@@ -193,11 +193,13 @@ void controls_init() {
|
||||
|
||||
SwitchesState get_switches_state() {
|
||||
SwitchesState result;
|
||||
for(size_t i=0; i<result.size(); i++) {
|
||||
for(size_t i=0; i<result.size()-1; i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
result[i] = switch_debounce[i].state();
|
||||
}
|
||||
|
||||
result[result.size()-1] = switch_debounce[switch_debounce.size()-1].state();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ enum class Switch {
|
||||
Down = 2,
|
||||
Up = 3,
|
||||
Sel = 4,
|
||||
Dfu = 5,
|
||||
};
|
||||
|
||||
using SwitchesState = std::bitset<5>;
|
||||
using SwitchesState = std::bitset<6>;
|
||||
|
||||
using EncoderPosition = uint32_t;
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* source: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/lz4-decompression-routine-for-cortex-m0-and-later */
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
/* License: Public Domain - I cannot be held responsible for what it does or does not do if you use it, whether it's modified or not. */
|
||||
/* Entry point = unlz4. On entry: r0 = source, r1 = destination. The first two bytes of the source must contain the length of the compressed data. */
|
||||
|
||||
.func unlz4
|
||||
.global unlz4,unlz4_len
|
||||
.type unlz4,%function
|
||||
.type unlz4_len,%function
|
||||
|
||||
.thumb_func
|
||||
unlz4: ldrh r2,[r0] /* get length of compressed data */
|
||||
adds r0,r0,#2 /* advance source pointer */
|
||||
|
||||
.thumb_func
|
||||
unlz4_len: push {r4-r6,lr} /* save r4, r5, r6 and return-address */
|
||||
adds r5,r2,r0 /* point r5 to end of compressed data */
|
||||
|
||||
getToken: ldrb r6,[r0] /* get token */
|
||||
adds r0,r0,#1 /* advance source pointer */
|
||||
lsrs r4,r6,#4 /* get literal length, keep token in r6 */
|
||||
beq getOffset /* jump forward if there are no literals */
|
||||
bl getLength /* get length of literals */
|
||||
movs r2,r0 /* point r2 to literals */
|
||||
bl copyData /* copy literals (r2=src, r1=dst, r4=len) */
|
||||
movs r0,r2 /* update source pointer */
|
||||
|
||||
getOffset: ldrb r3,[r0,#0] /* get match offset's low byte */
|
||||
subs r2,r1,r3 /* subtract from destination; this will become the match position */
|
||||
ldrb r3,[r0,#1] /* get match offset's high byte */
|
||||
lsls r3,r3,#8 /* shift to high byte */
|
||||
subs r2,r2,r3 /* subtract from match position */
|
||||
adds r0,r0,#2 /* advance source pointer */
|
||||
lsls r4,r6,#28 /* get rid of token's high 28 bits */
|
||||
lsrs r4,r4,#28 /* move the 4 low bits back where they were */
|
||||
bl getLength /* get length of match data */
|
||||
adds r4,r4,#4 /* minimum match length is 4 bytes */
|
||||
bl copyData /* copy match data (r2=src, r1=dst, r4=len) */
|
||||
cmp r0,r5 /* check if we've reached the end of the compressed data */
|
||||
blt getToken /* if not, go get the next token */
|
||||
pop {r4-r6,pc} /* restore r4, r5 and r6, then return */
|
||||
|
||||
.thumb_func
|
||||
getLength: cmp r4,#0x0f /* if length is 15, then more length info follows */
|
||||
bne gotLength /* jump forward if we have the complete length */
|
||||
|
||||
getLengthLoop: ldrb r3,[r0] /* read another byte */
|
||||
adds r0,r0,#1 /* advance source pointer */
|
||||
adds r4,r4,r3 /* add byte to length */
|
||||
cmp r3,#0xff /* check if end reached */
|
||||
beq getLengthLoop /* if not, go round loop */
|
||||
gotLength: bx lr /* return */
|
||||
|
||||
.thumb_func
|
||||
copyData: rsbs r4,r4,#0 /* index = -length */
|
||||
subs r2,r2,r4 /* point to end of source */
|
||||
subs r1,r1,r4 /* point to end of destination */
|
||||
|
||||
copyDataLoop: ldrb r3,[r2,r4] /* read byte from source_end[-index] */
|
||||
strb r3,[r1,r4] /* store byte in destination_end[-index] */
|
||||
adds r4,r4,#1 /* increment index */
|
||||
bne copyDataLoop /* keep going until index wraps to 0 */
|
||||
bx lr /* return */
|
||||
.size unlz4,.-unlz4
|
||||
.endfunc
|
||||
/* 42 narrow instructions = 84 bytes */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LZ4_H__
|
||||
#define __LZ4_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern void unlz4_len(const void *aSource, void *aDestination, uint32_t aLength);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*__LZ4_H__*/
|
||||
@@ -145,14 +145,14 @@ Continuous (Fox-oring)
|
||||
rffc507x::RFFC507x first_if;
|
||||
|
||||
static void event_loop() {
|
||||
ui::Context context;
|
||||
ui::SystemView system_view {
|
||||
static ui::Context context;
|
||||
static ui::SystemView system_view {
|
||||
context,
|
||||
portapack::display.screen_rect()
|
||||
};
|
||||
|
||||
EventDispatcher event_dispatcher { &system_view, context };
|
||||
MessageHandlerRegistration message_handler_display_sleep {
|
||||
static MessageHandlerRegistration message_handler_display_sleep {
|
||||
Message::ID::DisplaySleep,
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
|
||||
@@ -61,7 +61,7 @@ portapack::IO io {
|
||||
portapack::gpio_io_stbx,
|
||||
portapack::gpio_addr,
|
||||
portapack::gpio_lcd_te,
|
||||
portapack::gpio_unused,
|
||||
portapack::gpio_dfu,
|
||||
};
|
||||
|
||||
portapack::BacklightCAT4004 backlight_cat4004;
|
||||
@@ -489,10 +489,13 @@ bool init() {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
if( !portapack::cpld::update_if_necessary(portapack_cpld_config()) ) {
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_if_necessary(portapack_cpld_config());
|
||||
if ( result == portapack::cpld::CpldUpdateStatus::Program_failed ) {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
// If using a "2021/12 QFP100", press and hold the left button while booting. Should only need to do once.
|
||||
if (load_config() != 3 && load_config() != 4){
|
||||
// Mode left (R1) and right (R2,H2,H2+) bypass going into hackrf mode after failing CPLD update
|
||||
// Mode center (autodetect), up (R1) and down (R2,H2,H2+) will go into hackrf mode after failing CPLD update
|
||||
if (load_config() != 3 /* left */ && load_config() != 4 /* right */){
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
@@ -515,11 +518,13 @@ bool init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
void shutdown(const bool leave_screen_on) {
|
||||
gpdma::controller.disable();
|
||||
|
||||
backlight()->off();
|
||||
display.shutdown();
|
||||
if (!leave_screen_on) {
|
||||
backlight()->off();
|
||||
display.shutdown();
|
||||
}
|
||||
|
||||
radio::disable();
|
||||
audio::shutdown();
|
||||
|
||||
@@ -63,7 +63,7 @@ void set_antenna_bias(const bool v);
|
||||
bool get_antenna_bias();
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
void shutdown(const bool leave_screen_on = false);
|
||||
|
||||
Backlight* backlight();
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ using namespace hackrf::one;
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace radio {
|
||||
|
||||
@@ -168,7 +169,20 @@ void set_direction(const rf::Direction new_direction) {
|
||||
}
|
||||
|
||||
bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
const auto tuning_config = tuning::config::create(frequency);
|
||||
rf::Frequency final_frequency = frequency ;
|
||||
// if feature is enabled
|
||||
if( portapack::persistent_memory::config_converter() ) {
|
||||
//downconvert
|
||||
if( portapack::persistent_memory::config_updown_converter() )
|
||||
{
|
||||
final_frequency = frequency - portapack::persistent_memory::config_converter_freq();
|
||||
}
|
||||
else //upconvert
|
||||
{
|
||||
final_frequency = frequency + portapack::persistent_memory::config_converter_freq();
|
||||
}
|
||||
}
|
||||
const auto tuning_config = tuning::config::create(final_frequency);
|
||||
if( tuning_config.is_valid() ) {
|
||||
first_if.disable();
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ const tone_key_t tone_keys = {
|
||||
{ "8 YB", 88.500 },
|
||||
{ "9 ZZ", 91.500 },
|
||||
{ "10 ZA", 94.800 },
|
||||
{ "11 1ZB", 97.400 },
|
||||
{ "12 21Z", 100.000 },
|
||||
{ "11 ZB", 97.400 },
|
||||
{ "12 1Z", 100.000 },
|
||||
{ "13 1A", 103.500 },
|
||||
{ "14 1B", 107.200 },
|
||||
{ "15 2Z", 110.900 },
|
||||
{ "16 2Z", 114.800 },
|
||||
{ "16 2A", 114.800 },
|
||||
{ "17 2B", 118.800 },
|
||||
{ "18 3Z", 123.000 },
|
||||
{ "19 3A", 127.300 },
|
||||
|
||||
@@ -81,16 +81,21 @@ void Manager::feed(const Frame& frame) {
|
||||
const auto touch_raw = frame.touch;
|
||||
//const auto touch_stable = touch_debounce.state();
|
||||
const auto touch_stable = frame.touch;
|
||||
bool touch_pressure = false;
|
||||
bool touch_down_pressure = false;
|
||||
bool touch_up_pressure = false;
|
||||
|
||||
// Only feed coordinate averaging if there's a touch.
|
||||
// TODO: Separate threshold to gate coordinates for filtering?
|
||||
if( touch_raw ) {
|
||||
const auto metrics = calculate_metrics(frame);
|
||||
|
||||
// TODO: Add touch pressure hysteresis?
|
||||
touch_pressure = (metrics.r < r_touch_threshold);
|
||||
if( touch_pressure ) {
|
||||
constexpr float r_touch_down_threshold = 3200.0f;
|
||||
constexpr float r_touch_up_threshold = r_touch_down_threshold * 2.0f;
|
||||
|
||||
touch_down_pressure = (metrics.r < r_touch_down_threshold);
|
||||
touch_up_pressure = (metrics.r < r_touch_up_threshold);
|
||||
|
||||
if( touch_down_pressure ) {
|
||||
filter_x.feed(metrics.x * 1024);
|
||||
filter_y.feed(metrics.y * 1024);
|
||||
}
|
||||
@@ -101,7 +106,7 @@ void Manager::feed(const Frame& frame) {
|
||||
|
||||
switch(state) {
|
||||
case State::NoTouch:
|
||||
if( touch_stable && touch_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( touch_stable && touch_down_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( point_stable() ) {
|
||||
state = State::TouchDetected;
|
||||
touch_started();
|
||||
@@ -110,7 +115,7 @@ void Manager::feed(const Frame& frame) {
|
||||
break;
|
||||
|
||||
case State::TouchDetected:
|
||||
if( touch_stable && touch_pressure ) {
|
||||
if( touch_stable && touch_up_pressure ) {
|
||||
touch_moved();
|
||||
} else {
|
||||
state = State::NoTouch;
|
||||
|
||||
@@ -219,7 +219,6 @@ private:
|
||||
TouchDetected,
|
||||
};
|
||||
|
||||
static constexpr float r_touch_threshold = 640;
|
||||
static constexpr size_t touch_count_threshold { 3 };
|
||||
static constexpr uint32_t touch_stable_bound { 8 };
|
||||
|
||||
|
||||
@@ -30,10 +30,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
void AlphanumView::paint(Painter&) {
|
||||
draw_cursor();
|
||||
}
|
||||
|
||||
AlphanumView::AlphanumView(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
@@ -41,7 +37,7 @@ AlphanumView::AlphanumView(
|
||||
) : TextEntryView(nav, str, max_length)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
|
||||
add_children({
|
||||
&button_mode,
|
||||
&text_raw,
|
||||
@@ -77,16 +73,7 @@ AlphanumView::AlphanumView(
|
||||
field_raw.set_value('0');
|
||||
field_raw.on_select = [this](NumberField&) {
|
||||
char_add(field_raw.value());
|
||||
update_text();
|
||||
};
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
if (on_changed)
|
||||
on_changed(_str);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
update_text();
|
||||
}
|
||||
|
||||
void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
@@ -120,8 +107,6 @@ void AlphanumView::on_button(Button& button) {
|
||||
char_delete();
|
||||
else
|
||||
char_add(c);
|
||||
|
||||
update_text();
|
||||
}
|
||||
|
||||
bool AlphanumView::on_encoder(const EncoderEvent delta) {
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
AlphanumView& operator=(const AlphanumView&) = delete;
|
||||
AlphanumView& operator=(AlphanumView&&) = delete;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -163,6 +163,8 @@ void MenuView::clear() {
|
||||
}
|
||||
|
||||
menu_items.clear();
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
@@ -209,7 +211,7 @@ MenuItemView* MenuView::item_view(size_t index) const {
|
||||
bool MenuView::set_highlighted(int32_t new_value) {
|
||||
int32_t item_count = (int32_t)menu_items.size();
|
||||
|
||||
if (new_value < 0)
|
||||
if (new_value < 0 || item_count == 0)
|
||||
return false;
|
||||
|
||||
if (new_value >= item_count)
|
||||
@@ -238,7 +240,7 @@ bool MenuView::set_highlighted(int32_t new_value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t MenuView::highlighted_index() {
|
||||
uint32_t MenuView::highlighted_index() const {
|
||||
return highlighted_item;
|
||||
}
|
||||
|
||||
@@ -262,7 +264,7 @@ bool MenuView::on_key(const KeyEvent key) {
|
||||
case KeyEvent::Select:
|
||||
case KeyEvent::Right:
|
||||
if( menu_items[highlighted_item].on_select ) {
|
||||
menu_items[highlighted_item].on_select();
|
||||
menu_items[highlighted_item].on_select(key);
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ struct MenuItem {
|
||||
std::string text;
|
||||
ui::Color color;
|
||||
const Bitmap* bitmap;
|
||||
std::function<void(void)> on_select;
|
||||
std::function<void(KeyEvent)> on_select;
|
||||
|
||||
// TODO: Prevent default-constructed MenuItems.
|
||||
// I managed to construct a menu with three extra, unspecified menu items
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
MenuItemView* item_view(size_t index) const;
|
||||
|
||||
bool set_highlighted(int32_t new_value);
|
||||
uint32_t highlighted_index();
|
||||
uint32_t highlighted_index() const;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void on_focus() override;
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace ui {
|
||||
// show green peak value
|
||||
if( peak_enabled )
|
||||
{
|
||||
const Rect r5 { r.left(), r.bottom() + peak , r.width() , 1 };
|
||||
const Rect r5 { r.left() + peak - 3 , r.top() , 3 , r.height() };
|
||||
painter.fill_rectangle(
|
||||
r5,
|
||||
Color::green()
|
||||
@@ -160,7 +160,7 @@ namespace ui {
|
||||
// show green peak value if enabled
|
||||
if( peak_enabled )
|
||||
{
|
||||
const Rect r5 { r.left(), r.bottom() - peak , r.width() , 1 };
|
||||
const Rect r5 { r.left(), r.bottom() - peak - 3 , r.width() , 3 };
|
||||
painter.fill_rectangle(
|
||||
r5,
|
||||
Color::green()
|
||||
@@ -238,8 +238,96 @@ namespace ui {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void RSSIGraph::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
void RSSIGraph::paint(Painter& painter) {
|
||||
RSSIGraph_entry& prev_entry = graph_list[0];
|
||||
int xpos = 0 , prev_xpos = r.width();
|
||||
|
||||
for( int n = 1 ; (unsigned)n <= graph_list.size() ; n++ )
|
||||
{
|
||||
xpos = ( r.width() * (graph_list.size() - n ) ) / graph_list.size() ;
|
||||
int size = abs( xpos - prev_xpos );
|
||||
RSSIGraph_entry& entry = graph_list[ n - 1 ];
|
||||
|
||||
// black
|
||||
const Rect r0{ r.right() - prev_xpos , r.top() , size , r.height() };
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
Color::black());
|
||||
|
||||
// y_max
|
||||
int top_y_val = max( entry.rssi_max , prev_entry.rssi_max );
|
||||
int width_y = abs( entry.rssi_max - prev_entry.rssi_max );
|
||||
if( width_y == 0 )
|
||||
width_y = 1 ;
|
||||
const Point p1v{ r.right() - prev_xpos , r.bottom() - top_y_val };
|
||||
painter.draw_vline(
|
||||
p1v,
|
||||
width_y,
|
||||
Color::red());
|
||||
const Point p1h{ r.right() - prev_xpos , r.bottom() - entry.rssi_max };
|
||||
painter.draw_hline(
|
||||
p1h,
|
||||
size,
|
||||
Color::red());
|
||||
|
||||
// y_avg
|
||||
top_y_val = max( entry.rssi_avg , prev_entry.rssi_avg );
|
||||
width_y = abs( entry.rssi_avg - prev_entry.rssi_avg );
|
||||
if( width_y == 0 )
|
||||
width_y = 1 ;
|
||||
const Point p2v{ r.right() - prev_xpos , r.bottom() - top_y_val };
|
||||
painter.draw_vline(
|
||||
p2v,
|
||||
width_y,
|
||||
Color::white());
|
||||
const Point p2h{ r.right() - prev_xpos , r.bottom() - entry.rssi_avg };
|
||||
painter.draw_hline(
|
||||
p2h,
|
||||
size,
|
||||
Color::white());
|
||||
|
||||
// y_min
|
||||
top_y_val = max( entry.rssi_min , prev_entry.rssi_min );
|
||||
width_y = abs( entry.rssi_min - prev_entry.rssi_min );
|
||||
if( width_y == 0 )
|
||||
width_y = 1 ;
|
||||
const Point p3v{ r.right() - prev_xpos , r.bottom() - top_y_val };
|
||||
painter.draw_vline(
|
||||
p3v,
|
||||
width_y,
|
||||
Color::blue());
|
||||
const Point p3h{ r.right() - prev_xpos , r.bottom() - entry.rssi_min };
|
||||
painter.draw_hline(
|
||||
p3h,
|
||||
size,
|
||||
Color::blue());
|
||||
|
||||
// hack to display db
|
||||
top_y_val = max( entry.db , prev_entry.db );
|
||||
width_y = abs( entry.db - prev_entry.db );
|
||||
if( width_y == 0 )
|
||||
width_y = 1 ;
|
||||
const Point p4v{ r.right() - prev_xpos , r.bottom() - top_y_val };
|
||||
painter.draw_vline(
|
||||
p4v,
|
||||
width_y,
|
||||
Color::green());
|
||||
const Point p4h{ r.right() - prev_xpos , r.bottom() - entry.db };
|
||||
painter.draw_hline(
|
||||
p4h,
|
||||
size,
|
||||
Color::green());
|
||||
|
||||
prev_entry = entry ;
|
||||
prev_xpos = xpos ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*void RSSIGraph::paintOld(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
int16_t size = r.width() / nb_columns ;
|
||||
int16_t top_y_val = 0 ;
|
||||
@@ -307,7 +395,7 @@ namespace ui {
|
||||
r5,
|
||||
Color::black());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void RSSIGraph::add_values(int16_t rssi_min, int16_t rssi_avg, int16_t rssi_max, int16_t db )
|
||||
{
|
||||
@@ -340,12 +428,29 @@ namespace ui {
|
||||
{
|
||||
graph_list.erase( graph_list.begin() );
|
||||
}
|
||||
set_dirty();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void RSSIGraph::set_nb_columns( int16_t nb )
|
||||
{
|
||||
nb_columns = nb ;
|
||||
while( graph_list.size() > nb_columns )
|
||||
{
|
||||
graph_list.erase( graph_list.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIGraph::on_hide() {
|
||||
nb_columns_before_hide = nb_columns ;
|
||||
nb_columns = 1 ;
|
||||
while( graph_list.size() > nb_columns )
|
||||
{
|
||||
graph_list.erase( graph_list.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIGraph::on_show() {
|
||||
nb_columns = nb_columns_before_hide ;
|
||||
}
|
||||
|
||||
void RSSI::on_focus() {
|
||||
|
||||
@@ -115,8 +115,12 @@ namespace ui {
|
||||
void paint(Painter& painter) override;
|
||||
void add_values(int16_t rssi_min, int16_t rssi_avg, int16_t rssi_max, int16_t db );
|
||||
void set_nb_columns( int16_t nb );
|
||||
|
||||
void on_hide() override ;
|
||||
void on_show() override ;
|
||||
|
||||
private:
|
||||
uint16_t nb_columns_before_hide = 16 ;
|
||||
uint16_t nb_columns = 16 ;
|
||||
RSSIGraphList graph_list { } ;
|
||||
};
|
||||
|
||||
@@ -29,9 +29,25 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, const size_t max_length, const std::function<void(std::string&)> on_done) {
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
text_prompt(nav, str, str.length(), max_length, on_done);
|
||||
}
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
//if (persistent_memory::ui_config_textentry() == 0) {
|
||||
auto te_view = nav.push<AlphanumView>(str, max_length);
|
||||
te_view->set_cursor(cursor_pos);
|
||||
te_view->on_changed = [on_done](std::string& value) {
|
||||
if (on_done)
|
||||
on_done(value);
|
||||
@@ -45,76 +61,169 @@ void text_prompt(NavigationView& nav, std::string& str, const size_t max_length,
|
||||
}*/
|
||||
}
|
||||
|
||||
void TextEntryView::update_text() {
|
||||
if (cursor_pos < 30)
|
||||
text_input.set(_str + std::string(_max_length - _str.length(), ' '));
|
||||
else
|
||||
text_input.set('<' + _str.substr(cursor_pos - 29, 29));
|
||||
|
||||
draw_cursor();
|
||||
/* TextField ***********************************************************/
|
||||
|
||||
TextField::TextField(
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
Point position,
|
||||
uint32_t length
|
||||
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
|
||||
text_{ str },
|
||||
max_length_{ std::max<size_t>(max_length, str.length()) },
|
||||
char_count_{ std::max<uint32_t>(length, 1) },
|
||||
cursor_pos_{ text_.length() },
|
||||
insert_mode_{ true }
|
||||
{
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
|
||||
void TextField::set_overwrite_mode() {
|
||||
insert_mode_ = false;
|
||||
}
|
||||
|
||||
void TextField::char_add(char c) {
|
||||
// Don't add if inserting and at max_length and
|
||||
// don't overwrite if past the end of the text.
|
||||
if ((text_.length() >= max_length_ && insert_mode_) ||
|
||||
(cursor_pos_ >= text_.length() && !insert_mode_))
|
||||
return;
|
||||
|
||||
if (insert_mode_)
|
||||
text_.insert(cursor_pos_, 1, c);
|
||||
else
|
||||
text_[cursor_pos_] = c;
|
||||
|
||||
cursor_pos_++;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::char_delete() {
|
||||
if (cursor_pos_ == 0)
|
||||
return;
|
||||
|
||||
cursor_pos_--;
|
||||
text_.erase(cursor_pos_, 1);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::paint(Painter& painter) {
|
||||
constexpr int char_width = 8;
|
||||
|
||||
auto rect = screen_rect();
|
||||
auto text_style = has_focus() ? style().invert() : style();
|
||||
auto offset = 0;
|
||||
|
||||
// Does the string need to be shifted?
|
||||
if (cursor_pos_ >= char_count_)
|
||||
offset = cursor_pos_ - char_count_ + 1;
|
||||
|
||||
// Clear the control.
|
||||
painter.fill_rectangle(rect, text_style.background);
|
||||
|
||||
// Draw the text starting at the offset.
|
||||
for (uint32_t i = 0; i < char_count_ && i + offset < text_.length(); i++) {
|
||||
painter.draw_char(
|
||||
{ rect.location().x() + (static_cast<int>(i) * char_width), rect.location().y() },
|
||||
text_style,
|
||||
text_[i + offset]
|
||||
);
|
||||
}
|
||||
|
||||
// Determine cursor position on screen (either the cursor position or the last char).
|
||||
int32_t cursor_x = char_width * (offset > 0 ? char_count_ - 1 : cursor_pos_);
|
||||
Point cursor_point{ screen_pos().x() + cursor_x, screen_pos().y() };
|
||||
auto cursor_style = text_style.invert();
|
||||
|
||||
// Invert the cursor character when in overwrite mode.
|
||||
if (!insert_mode_ && (cursor_pos_) < text_.length())
|
||||
painter.draw_char(cursor_point, cursor_style, text_[cursor_pos_]);
|
||||
|
||||
// Draw the cursor.
|
||||
Rect cursor_box{ cursor_point, { char_width, 16 } };
|
||||
painter.draw_rectangle(cursor_box, cursor_style.background);
|
||||
}
|
||||
|
||||
bool TextField::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Left && cursor_pos_ > 0)
|
||||
cursor_pos_--;
|
||||
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
|
||||
cursor_pos_++;
|
||||
else if (key == KeyEvent::Select)
|
||||
insert_mode_ = !insert_mode_;
|
||||
else
|
||||
return false;
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_pos = cursor_pos_ + delta;
|
||||
|
||||
// Let the encoder wrap around the ends of the text.
|
||||
if (new_pos < 0)
|
||||
new_pos = text_.length();
|
||||
else if (static_cast<size_t>(new_pos) > text_.length())
|
||||
new_pos = 0;
|
||||
|
||||
set_cursor(new_pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextEntryView ***********************************************************/
|
||||
|
||||
void TextEntryView::char_delete() {
|
||||
if (!cursor_pos) return;
|
||||
|
||||
cursor_pos--;
|
||||
_str.resize(cursor_pos);
|
||||
text_input.char_delete();
|
||||
}
|
||||
|
||||
void TextEntryView::char_add(const char c) {
|
||||
if (cursor_pos >= _max_length) return;
|
||||
|
||||
_str += c;
|
||||
cursor_pos++;
|
||||
text_input.char_add(c);
|
||||
}
|
||||
|
||||
void TextEntryView::draw_cursor() {
|
||||
Point draw_pos;
|
||||
|
||||
draw_pos = { text_input.screen_rect().location().x() + std::min((Coord)cursor_pos, (Coord)28) * 8,
|
||||
text_input.screen_rect().location().y() + 16 };
|
||||
|
||||
// Erase previous
|
||||
display.fill_rectangle(
|
||||
{ { text_input.screen_rect().location().x(), draw_pos.y() }, { text_input.screen_rect().size().width(), 4 } },
|
||||
Color::black()
|
||||
);
|
||||
// Draw new
|
||||
display.fill_rectangle(
|
||||
{ draw_pos, { 8, 4 } },
|
||||
Color::white()
|
||||
);
|
||||
void TextEntryView::set_cursor(uint32_t pos) {
|
||||
text_input.set_cursor(pos);
|
||||
}
|
||||
|
||||
void TextEntryView::focus() {
|
||||
button_ok.focus();
|
||||
text_input.focus();
|
||||
}
|
||||
|
||||
TextEntryView::TextEntryView(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length
|
||||
) : _str(str),
|
||||
_max_length(max_length)
|
||||
) : text_input{ str, max_length, { 0, 0 } }
|
||||
{
|
||||
|
||||
// Trim from right
|
||||
//_str->erase(std::find_if(_str->rbegin(), _str->rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), _str->end());
|
||||
if (_str.length() > _max_length)
|
||||
_str.resize(_max_length);
|
||||
_str.reserve(_max_length);
|
||||
|
||||
cursor_pos = _str.length();
|
||||
|
||||
add_children({
|
||||
&text_input,
|
||||
&button_ok
|
||||
});
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
_str.resize(cursor_pos);
|
||||
|
||||
button_ok.on_select = [this, &str, &nav](Button&) {
|
||||
if (on_changed)
|
||||
on_changed(_str);
|
||||
on_changed(str);
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,12 +28,60 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
// A TextField is bound to a string reference and allows the string
|
||||
// to be manipulated. The field itself does not provide the UI for
|
||||
// setting the value. It provides the UI of rendering the text,
|
||||
// a cursor, and an API to edit the string content.
|
||||
class TextField : public Widget {
|
||||
public:
|
||||
TextField(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextField{str, 64, position, length} { }
|
||||
|
||||
// Str: the string containing the content to edit.
|
||||
// Max_length: max length the string is allowed to use.
|
||||
// Position: the top-left corner of the control.
|
||||
// Length: the number of characters to display.
|
||||
// - Characters are 8 pixels wide.
|
||||
// - The screen can show 30 characters max.
|
||||
// - The control is 16 pixels tall.
|
||||
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
|
||||
TextField(const TextField&) = delete;
|
||||
TextField(TextField&&) = delete;
|
||||
TextField& operator=(const TextField&) = delete;
|
||||
TextField& operator=(TextField&&) = delete;
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
void set_insert_mode();
|
||||
void set_overwrite_mode();
|
||||
|
||||
void char_add(char c);
|
||||
void char_delete();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
protected:
|
||||
std::string& text_;
|
||||
size_t max_length_;
|
||||
uint32_t char_count_;
|
||||
uint32_t cursor_pos_;
|
||||
bool insert_mode_;
|
||||
};
|
||||
|
||||
class TextEntryView : public View {
|
||||
public:
|
||||
std::function<void(std::string&)> on_changed { };
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Text entry"; };
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
|
||||
protected:
|
||||
TextEntryView(NavigationView& nav, std::string& str, size_t max_length);
|
||||
@@ -45,24 +93,30 @@ protected:
|
||||
|
||||
void char_add(const char c);
|
||||
void char_delete();
|
||||
void draw_cursor();
|
||||
void update_text();
|
||||
|
||||
std::string& _str;
|
||||
size_t _max_length;
|
||||
uint32_t cursor_pos { 0 };
|
||||
|
||||
Text text_input {
|
||||
{ 0, 0, 240, 16 }
|
||||
};
|
||||
|
||||
TextField text_input;
|
||||
Button button_ok {
|
||||
{ 10 * 8, 33 * 8, 9 * 8, 32 },
|
||||
"OK"
|
||||
};
|
||||
};
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, size_t max_length, const std::function<void(std::string&)> on_done = nullptr);
|
||||
// Show the TextEntry view to receive keyboard input.
|
||||
// NB: This function returns immediately. 'str' is taken
|
||||
// by reference and its lifetime must be ensured by the
|
||||
// caller until the TextEntry view is dismissed.
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "bmp_splash.hpp"
|
||||
#include "bmp_modal_warning.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "ui_about_simple.hpp"
|
||||
#include "ui_adsb_rx.hpp"
|
||||
@@ -70,6 +71,8 @@
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_sd_over_usb.hpp"
|
||||
|
||||
//#include "acars_app.hpp"
|
||||
#include "ais_app.hpp"
|
||||
@@ -111,6 +114,7 @@ SystemStatusView::SystemStatusView(
|
||||
&button_back,
|
||||
&title,
|
||||
&button_title,
|
||||
&button_converter,
|
||||
&button_speaker,
|
||||
&button_stealth,
|
||||
//&button_textentry,
|
||||
@@ -126,6 +130,24 @@ SystemStatusView::SystemStatusView(
|
||||
else
|
||||
button_speaker.hidden(true);
|
||||
|
||||
|
||||
if( portapack::persistent_memory::config_hide_converter() )
|
||||
{
|
||||
button_converter.hidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
button_converter.hidden(false);
|
||||
if( portapack::persistent_memory::config_converter() )
|
||||
{
|
||||
button_converter.set_foreground(Color::red());
|
||||
}
|
||||
else
|
||||
{
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
}
|
||||
|
||||
button_back.id = -1; // Special ID used by FocusManager
|
||||
title.set_style(&style_systemstatus);
|
||||
|
||||
@@ -148,6 +170,10 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_title();
|
||||
};
|
||||
|
||||
button_converter.on_select = [this](ImageButton&) {
|
||||
this->on_converter();
|
||||
};
|
||||
|
||||
button_speaker.on_select = [this](ImageButton&) {
|
||||
this->on_speaker();
|
||||
};
|
||||
@@ -179,6 +205,34 @@ SystemStatusView::SystemStatusView(
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
if( portapack::persistent_memory::config_hide_converter() )
|
||||
{
|
||||
button_converter.hidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( portapack::persistent_memory::config_updown_converter() )
|
||||
{
|
||||
button_converter.set_bitmap(&bitmap_icon_downconvert);
|
||||
}
|
||||
else
|
||||
{
|
||||
button_converter.set_bitmap(&bitmap_icon_upconvert);
|
||||
}
|
||||
button_converter.hidden(false);
|
||||
if( portapack::persistent_memory::config_converter() )
|
||||
{
|
||||
button_converter.set_foreground(Color::red());
|
||||
}
|
||||
else
|
||||
{
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
}
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() );
|
||||
transmitter_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() );
|
||||
|
||||
if (!portapack::persistent_memory::config_speaker()) {
|
||||
button_speaker.set_foreground(Color::light_grey());
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker_mute);
|
||||
@@ -187,6 +241,7 @@ void SystemStatusView::refresh() {
|
||||
else {
|
||||
button_speaker.hidden(true);
|
||||
}
|
||||
|
||||
if (portapack::get_antenna_bias()) {
|
||||
button_bias_tee.set_bitmap(&bitmap_icon_biast_on);
|
||||
button_bias_tee.set_foreground(ui::Color::yellow());
|
||||
@@ -202,6 +257,7 @@ void SystemStatusView::refresh() {
|
||||
button_clock_status.set_bitmap(&bitmap_icon_clk_int);
|
||||
// button_bias_tee.set_foreground(ui::Color::green());
|
||||
}
|
||||
|
||||
if(portapack::persistent_memory::clkout_enabled()) {
|
||||
button_clock_status.set_foreground(ui::Color::green());
|
||||
} else {
|
||||
@@ -239,6 +295,20 @@ void SystemStatusView::set_title(const std::string new_value) {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::on_converter() {
|
||||
if(!portapack::persistent_memory::config_converter())
|
||||
{
|
||||
portapack::persistent_memory::set_config_converter( true );
|
||||
button_converter.set_foreground(Color::red());
|
||||
}
|
||||
else
|
||||
{
|
||||
portapack::persistent_memory::set_config_converter( false );
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() ); // Retune
|
||||
}
|
||||
|
||||
void SystemStatusView::on_speaker() {
|
||||
if (!portapack::speaker_mode)
|
||||
{
|
||||
@@ -467,26 +537,26 @@ void NavigationView::focus() {
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items( {
|
||||
{ "ADS-B", ui::Color::green(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, },
|
||||
//{ "ACARS", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{ "AIS Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } },
|
||||
{ "AFSK", ui::Color::yellow(), &bitmap_icon_modem, [&nav](){ nav.push<AFSKRxView>(); } },
|
||||
{ "BTLE", ui::Color::yellow(), &bitmap_icon_btle, [&nav](){ nav.push<BTLERxView>(); } },
|
||||
{ "NRF", ui::Color::yellow(), &bitmap_icon_nrf, [&nav](){ nav.push<NRFRxView>(); } },
|
||||
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(); } },
|
||||
{ "Analog TV", ui::Color::yellow(), &bitmap_icon_sstv, [&nav](){ nav.push<AnalogTvView>(); } },
|
||||
{ "ERT Meter", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push<ERTAppView>(); } },
|
||||
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } },
|
||||
{ "Radiosnde", ui::Color::green(), &bitmap_icon_sonde, [&nav](){ nav.push<SondeView>(); } },
|
||||
{ "TPMS Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.push<TPMSAppView>(); } },
|
||||
{ "Recon", ui::Color::green(), &bitmap_icon_scanner, [&nav](){ nav.push<ReconView>(); } },
|
||||
{ "Level", ui::Color::green(), &bitmap_icon_options_radio, [&nav](){ nav.push<LevelView>(); } },
|
||||
{ "APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSRXView>(); } }
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items( {
|
||||
{ "ADS-B", ui::Color::green(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, },
|
||||
//{ "ACARS", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{ "AIS Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } },
|
||||
{ "AFSK", ui::Color::yellow(), &bitmap_icon_modem, [&nav](){ nav.push<AFSKRxView>(); } },
|
||||
{ "BTLE", ui::Color::yellow(), &bitmap_icon_btle, [&nav](){ nav.push<BTLERxView>(); } },
|
||||
{ "NRF", ui::Color::yellow(), &bitmap_icon_nrf, [&nav](){ nav.push<NRFRxView>(); } },
|
||||
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(); } },
|
||||
{ "Analog TV", ui::Color::yellow(), &bitmap_icon_sstv, [&nav](){ nav.push<AnalogTvView>(); } },
|
||||
{ "ERT Meter", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push<ERTAppView>(); } },
|
||||
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } },
|
||||
{ "Radiosnde", ui::Color::green(), &bitmap_icon_sonde, [&nav](){ nav.push<SondeView>(); } },
|
||||
{ "TPMS Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.push<TPMSAppView>(); } },
|
||||
{ "Recon", ui::Color::green(), &bitmap_icon_scanner, [&nav](){ nav.push<ReconView>(); } },
|
||||
{ "Level", ui::Color::green(), &bitmap_icon_options_radio, [&nav](){ nav.push<LevelView>(); } },
|
||||
{ "APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSRXView>(); } }
|
||||
/*
|
||||
{ "DMR", ui::Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SIGFOX", ui::Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); } }, // SIGFRXView
|
||||
@@ -501,30 +571,29 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
/* TransmittersMenuView **************************************************/
|
||||
|
||||
TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items({
|
||||
{ "ADS-B [S]", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBTxView>(); } },
|
||||
{ "APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSTXView>(); } },
|
||||
{ "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push<BHTView>(); } },
|
||||
{ "GPS Sim", ui::Color::yellow(), &bitmap_icon_gps_sim, [&nav](){ nav.push<GpsSimAppView>(); } },
|
||||
{ "Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
||||
{ "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); } },
|
||||
{ "LGE tool", ui::Color::yellow(), &bitmap_icon_lge, [&nav](){ nav.push<LGEView>(); } },
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items({
|
||||
{ "ADS-B [S]", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBTxView>(); } },
|
||||
{ "APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSTXView>(); } },
|
||||
{ "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push<BHTView>(); } },
|
||||
{ "GPS Sim", ui::Color::yellow(), &bitmap_icon_gps_sim, [&nav](){ nav.push<GpsSimAppView>(); } },
|
||||
{ "Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
||||
{ "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); } },
|
||||
{ "LGE tool", ui::Color::yellow(), &bitmap_icon_lge, [&nav](){ nav.push<LGEView>(); } },
|
||||
{ "Morse", ui::Color::green(), &bitmap_icon_morse, [&nav](){ nav.push<MorseView>(); } },
|
||||
{ "BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav](){ nav.push<CoasterPagerView>(); } },
|
||||
{ "BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav](){ nav.push<CoasterPagerView>(); } },
|
||||
//{ "Nuoptix DTMF", ui::Color::green(), &bitmap_icon_nuoptix, [&nav](){ nav.push<NuoptixView>(); } },
|
||||
{ "OOK", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<EncodersView>(); } },
|
||||
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGTXView>(); } },
|
||||
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGTXView>(); } },
|
||||
{ "RDS", ui::Color::green(), &bitmap_icon_rds, [&nav](){ nav.push<RDSView>(); } },
|
||||
{ "Soundbrd", ui::Color::green(), &bitmap_icon_soundboard,[&nav](){ nav.push<SoundBoardView>(); } },
|
||||
{ "SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } },
|
||||
{ "TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav](){ nav.push<LCRView>(); } },
|
||||
{ "Soundbrd", ui::Color::green(), &bitmap_icon_soundboard,[&nav](){ nav.push<SoundBoardView>(); } },
|
||||
{ "SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } },
|
||||
{ "TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav](){ nav.push<LCRView>(); } },
|
||||
{ "TouchTune", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<TouchTunesView>(); } },
|
||||
{ "Playlist", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<PlaylistView>(); } },
|
||||
|
||||
{ "Playlist", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<PlaylistView>(); } },
|
||||
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
|
||||
});
|
||||
}
|
||||
@@ -532,10 +601,10 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
/* UtilitiesMenuView *****************************************************/
|
||||
|
||||
UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
if( portapack::persistent_memory::show_gui_return_icon() )
|
||||
{
|
||||
add_items( { { "..", ui::Color::light_grey(),&bitmap_icon_previous , [&nav](){ nav.pop(); } } } );
|
||||
}
|
||||
add_items({
|
||||
//{ "Test app", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); } },
|
||||
{ "Freq. manager", ui::Color::green(), &bitmap_icon_freqman, [&nav](){ nav.push<FrequencyManagerView>(); } },
|
||||
@@ -547,6 +616,8 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
{ "Antenna length", ui::Color::green(), &bitmap_icon_tools_antenna, [&nav](){ nav.push<WhipCalcView>(); } },
|
||||
|
||||
{ "Wipe SD card", ui::Color::red(), &bitmap_icon_tools_wipesd, [&nav](){ nav.push<WipeSDView>(); } },
|
||||
{ "Flash Utility", ui::Color::red(), &bitmap_icon_temperature, [&nav](){ nav.push<FlashUtilityView>(); } },
|
||||
{ "SD over USB", ui::Color::yellow(), &bitmap_icon_hackrf, [&nav](){ nav.push<SdOverUsbView>(); } },
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -570,7 +641,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
{ "Transmit", ui::Color::cyan(), &bitmap_icon_transmit, [&nav](){ nav.push<TransmittersMenuView>(); } },
|
||||
{ "Capture", ui::Color::red(), &bitmap_icon_capture, [&nav](){ nav.push<CaptureAppView>(); } },
|
||||
{ "Replay", ui::Color::green(), &bitmap_icon_replay, [&nav](){ nav.push<ReplayAppView>(); } },
|
||||
{ "Search", ui::Color::yellow(), &bitmap_icon_search, [&nav](){ nav.push<SearchView>(); } },
|
||||
{ "Search", ui::Color::yellow(), &bitmap_icon_search, [&nav](){ nav.push<SearchView>(); } },
|
||||
{ "Scanner", ui::Color::yellow(), &bitmap_icon_scanner, [&nav](){ nav.push<ScannerView>(); } },
|
||||
{ "Microphone", ui::Color::yellow(), &bitmap_icon_microphone,[&nav](){ nav.push<MicTXView>(); } },
|
||||
{ "Looking Glass", ui::Color::yellow(), &bitmap_icon_looking, [&nav](){ nav.push<GlassView>(); } },
|
||||
@@ -653,6 +724,14 @@ SystemView::SystemView(
|
||||
|
||||
navigation_view.push<SystemMenuView>();
|
||||
|
||||
File pmem_flag_file_handle ;
|
||||
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG" ;
|
||||
auto result = pmem_flag_file_handle.open(pmem_flag_file);
|
||||
if(!result.is_valid())
|
||||
{
|
||||
portapack::persistent_memory::load_persistent_settings_from_file("SETTINGS/pmem_settings");
|
||||
}
|
||||
|
||||
if (portapack::persistent_memory::config_splash())
|
||||
{
|
||||
navigation_view.push<BMPView>();
|
||||
@@ -670,6 +749,36 @@ Context& SystemView::context() const {
|
||||
return context_;
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
if (overlay_active){
|
||||
this->remove_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
}
|
||||
else{
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
}
|
||||
|
||||
overlay_active = !overlay_active;
|
||||
}
|
||||
|
||||
void SystemView::paint_overlay() {
|
||||
static bool last_paint_state = false;
|
||||
if (overlay_active){
|
||||
// paint background only every other second
|
||||
if ((((chTimeNow()>>10) & 0x01) == 0x01) == last_paint_state)
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
this->overlay.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/* ***********************************************************************/
|
||||
|
||||
void BMPView::focus() {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "ui_channel.hpp"
|
||||
#include "ui_audio.hpp"
|
||||
#include "ui_sd_card_status_view.hpp"
|
||||
#include "ui_dfu_menu.hpp"
|
||||
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
@@ -75,6 +76,15 @@ namespace ui
|
||||
{
|
||||
return reinterpret_cast<T *>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
// Pushes a new view under the current on the stack so the current view returns into this new one.
|
||||
template <class T, class... Args>
|
||||
void push_under_current(Args &&...args)
|
||||
{
|
||||
auto new_view = std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...));
|
||||
view_stack.insert(view_stack.end() - 1, std::move(new_view));
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
T *replace(Args &&...args)
|
||||
{
|
||||
@@ -125,7 +135,7 @@ namespace ui
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_back{
|
||||
{2, 0 * 16, 16, 16},
|
||||
{0, 0 * 16, 12 * 8, 16},//back button is long enough to cover the title area to make it easier to touch
|
||||
&bitmap_icon_previous,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
@@ -142,11 +152,18 @@ namespace ui
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_speaker{
|
||||
{17 * 8, 0, 2 * 8, 1 * 16},
|
||||
{15 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_mute,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_converter{
|
||||
{17 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_upconvert,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
ImageButton button_stealth{
|
||||
{19 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_stealth,
|
||||
@@ -187,6 +204,7 @@ namespace ui
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{28 * 8, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
void on_converter();
|
||||
void on_speaker();
|
||||
void on_stealth();
|
||||
void on_bias_tee();
|
||||
@@ -282,10 +300,15 @@ namespace ui
|
||||
const Rect parent_rect);
|
||||
|
||||
Context &context() const override;
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
|
||||
private:
|
||||
bool overlay_active {false};
|
||||
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
DfuMenu overlay{navigation_view};
|
||||
NavigationView navigation_view{};
|
||||
Context &context_;
|
||||
};
|
||||
|
||||
@@ -90,8 +90,6 @@ set(CSRC
|
||||
${PORTSRC}
|
||||
${KERNSRC}
|
||||
${TESTSRC}
|
||||
${HALSRC}
|
||||
${PLATFORMSRC}
|
||||
${BOARDSRC}
|
||||
)
|
||||
|
||||
@@ -141,6 +139,7 @@ set(CPPSRC
|
||||
${COMMON}/chibios_cpp.cpp
|
||||
debug.cpp
|
||||
${COMMON}/gcc.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
tone_gen.cpp
|
||||
)
|
||||
|
||||
@@ -189,7 +188,7 @@ set(AOPT)
|
||||
set(TOPT "-mthumb -DTHUMB")
|
||||
|
||||
# Define C warning options here
|
||||
set(CWARN "-Wall -Wextra -Wstrict-prototypes")
|
||||
set(CWARN "-Wall -Wextra")
|
||||
|
||||
# Define C++ warning options here
|
||||
set(CPPWARN "-Wall -Wextra")
|
||||
@@ -262,10 +261,11 @@ macro(DeclareTargets chunk_tag name)
|
||||
project("baseband_${name}")
|
||||
|
||||
include(${RULESPATH}/rules.cmake)
|
||||
add_executable(${PROJECT_NAME}.elf $<TARGET_OBJECTS:baseband_shared> ${MODE_CPPSRC})
|
||||
add_executable(${PROJECT_NAME}.elf $<TARGET_OBJECTS:baseband_shared> ${MODE_CPPSRC} ${HALSRC} ${PLATFORMSRC})
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
||||
add_definitions(${DEFS})
|
||||
include_directories(. ${INCDIR})
|
||||
target_compile_definitions(${PROJECT_NAME}.elf PUBLIC "-DBASEBAND_${name}")
|
||||
include_directories(. ${INCDIR} ${MODE_INCDIR})
|
||||
link_directories(${LLIBDIR})
|
||||
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
|
||||
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
|
||||
@@ -273,7 +273,8 @@ macro(DeclareTargets chunk_tag name)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_NAME}.bin ${PROJECT_NAME}.img
|
||||
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
|
||||
COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.bin ${chunk_tag} ${PROJECT_NAME}.img
|
||||
COMMAND ${LZ4} -f -9 ${PROJECT_NAME}.bin ${PROJECT_NAME}.lz4
|
||||
COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.lz4 ${chunk_tag} ${PROJECT_NAME}.img
|
||||
DEPENDS ${PROJECT_NAME}.elf ${MAKE_IMAGE_CHUNK}
|
||||
VERBATIM
|
||||
)
|
||||
@@ -504,11 +505,70 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PWFM wfm_audio)
|
||||
|
||||
### Flash Utility
|
||||
|
||||
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
|
||||
set(MODE_INCDIR ${FATFSINC})
|
||||
set(MODE_CPPSRC
|
||||
proc_flash_utility.cpp
|
||||
w25q80bv.cpp
|
||||
${FATFSSRC}
|
||||
)
|
||||
DeclareTargets(PFUT flash_utility)
|
||||
|
||||
### SD over USB
|
||||
|
||||
set(MODE_INCDIR
|
||||
${HACKRF_PATH}/firmware
|
||||
${HACKRF_PATH}/firmware/common
|
||||
${HACKRF_PATH}/firmware/libopencm3/include
|
||||
)
|
||||
set(MODE_CPPSRC
|
||||
sd_over_usb/proc_sd_over_usb.cpp
|
||||
|
||||
sd_over_usb/scsi.c
|
||||
sd_over_usb/diskio.c
|
||||
sd_over_usb/sd_over_usb.c
|
||||
sd_over_usb/usb_descriptor.c
|
||||
sd_over_usb/hackrf_core.c
|
||||
|
||||
${HACKRF_PATH}/firmware/common/usb.c
|
||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_endpoint.c
|
||||
${HACKRF_PATH}/firmware/common/usb_request.c
|
||||
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
||||
${HACKRF_PATH}/firmware/common/platform_detect.c
|
||||
${HACKRF_PATH}/firmware/common/gpio_lpc.c
|
||||
${HACKRF_PATH}/firmware/common/firmware_info.c
|
||||
${HACKRF_PATH}/firmware/common/si5351c.c
|
||||
${HACKRF_PATH}/firmware/common/i2c_bus.c
|
||||
${HACKRF_PATH}/firmware/common/mixer.c
|
||||
${HACKRF_PATH}/firmware/common/clkin.c
|
||||
${HACKRF_PATH}/firmware/common/spi_bus.c
|
||||
${HACKRF_PATH}/firmware/common/sgpio.c
|
||||
${HACKRF_PATH}/firmware/common/rf_path.c
|
||||
${HACKRF_PATH}/firmware/common/i2c_lpc.c
|
||||
${HACKRF_PATH}/firmware/common/spi_ssp.c
|
||||
${HACKRF_PATH}/firmware/common/rffc5071_spi.c
|
||||
${HACKRF_PATH}/firmware/common/rffc5071.c
|
||||
${HACKRF_PATH}/firmware/common/clkin.c
|
||||
${HACKRF_PATH}/firmware/common/gpdma.c
|
||||
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/cm3/nvic.c
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/cm3/sync.c
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/scu.c
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/timer.c
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/i2c.c
|
||||
)
|
||||
DeclareTargets(PUSB sd_over_usb)
|
||||
|
||||
### HackRF "factory" firmware
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT hackrf.img
|
||||
COMMAND ${MAKE_IMAGE_CHUNK} ${HACKRF_FIRMWARE_BIN_IMAGE} HRF1 hackrf.img 98304
|
||||
COMMAND ${LZ4} -f -9 ${HACKRF_FIRMWARE_BIN_IMAGE} hackrf.lz4
|
||||
COMMAND ${MAKE_IMAGE_CHUNK} hackrf.lz4 HRF1 hackrf.img
|
||||
DEPENDS ${HACKRF_FIRMWARE_BIN_FILENAME} ${MAKE_IMAGE_CHUNK}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "baseband_dma.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -103,8 +104,12 @@ static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gp
|
||||
|
||||
static ThreadWait thread_wait;
|
||||
|
||||
volatile uint32_t buffer_transfered = 0;
|
||||
volatile uint32_t buffer_handled = 0;
|
||||
|
||||
static void transfer_complete() {
|
||||
const auto next_lli_index = gpdma_channel_sgpio.next_lli() - &lli_loop[0];
|
||||
buffer_transfered++;
|
||||
thread_wait.wake_from_interrupt(next_lli_index);
|
||||
}
|
||||
|
||||
@@ -158,6 +163,10 @@ void disable() {
|
||||
|
||||
baseband::buffer_t wait_for_buffer() {
|
||||
const auto next_index = thread_wait.sleep();
|
||||
buffer_handled++;
|
||||
|
||||
auto buffer_missed = buffer_transfered - buffer_handled;
|
||||
shared_memory.m4_buffer_missed = buffer_missed;
|
||||
|
||||
if( next_index >= 0 ) {
|
||||
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
|
||||
@@ -508,6 +508,8 @@
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief System tick event hook.
|
||||
* @details This hook is invoked in the system tick handler immediately
|
||||
@@ -516,6 +518,8 @@
|
||||
#if !defined(SYSTEM_TICK_EVENT_HOOK) || defined(__DOXYGEN__)
|
||||
#define SYSTEM_TICK_EVENT_HOOK() { \
|
||||
/* System tick event code here.*/ \
|
||||
extern void update_performance_counters(); \
|
||||
update_performance_counters(); \
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <hal.h>
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
|
||||
void write_m4_panic_msg(const char *panic_message, struct extctx *ctxp) {
|
||||
if (ctxp == nullptr) {
|
||||
@@ -116,5 +117,28 @@ CH_IRQ_HANDLER(HardFaultVector) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_performance_counters() {
|
||||
auto performance_counter_active = shared_memory.request_m4_performance_counter;
|
||||
if (performance_counter_active == 0x00)
|
||||
return;
|
||||
|
||||
static bool last_paint_state = false;
|
||||
if ((((chTimeNow()>>10) & 0x01) == 0x01) == last_paint_state)
|
||||
return;
|
||||
|
||||
// Idle thread state is sometimes unuseable
|
||||
if (chThdGetTicks(chSysGetIdleThread()) > 0x10000000)
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
auto free_stack = (uint32_t)get_free_stack_space();
|
||||
auto free_heap = chCoreStatus();
|
||||
|
||||
shared_memory.m4_cpu_usage = utilisation;
|
||||
shared_memory.m4_stack_usage = free_stack;
|
||||
shared_memory.m4_heap_usage = free_heap;
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -36,6 +36,7 @@ inline uint32_t get_free_stack_space(){
|
||||
return stack_space_left;
|
||||
}
|
||||
|
||||
/* Executes a breakpoint only when a debugger is attached. */
|
||||
#define HALT_IF_DEBUGGING() \
|
||||
do { \
|
||||
if ((*(volatile uint32_t *)0xE000EDF0) & (1 << 0)) { \
|
||||
@@ -43,4 +44,9 @@ inline uint32_t get_free_stack_space(){
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Stops execution until a debugger is attached. */
|
||||
#define HALT_UNTIL_DEBUGGING() \
|
||||
while (!((*(volatile uint32_t *)0xE000EDF0) & (1 << 0))) {} \
|
||||
__asm__ __volatile__("bkpt 1")
|
||||
|
||||
#endif/*__DEBUG_H__*/
|
||||
|
||||
@@ -83,6 +83,12 @@ SSB::SSB() : hilbert() {
|
||||
}
|
||||
|
||||
void SSB::execute(const buffer_s16_t& audio, const buffer_c8_t& buffer, bool& configured_in, uint32_t& new_beep_index, uint32_t& new_beep_timer,TXProgressMessage& new_txprogress_message, AudioLevelReportMessage& new_level_message, uint32_t& new_power_acc_count, uint32_t& new_divider ) {
|
||||
//unused
|
||||
(void)configured_in ;
|
||||
(void)new_beep_index ;
|
||||
(void)new_beep_timer ;
|
||||
(void)new_txprogress_message ;
|
||||
|
||||
// No way to activate correctly the roger beep in this option, Maybe not enough M4 CPU power , Let's block roger beep in SSB selection by now .
|
||||
int32_t sample = 0;
|
||||
int8_t re = 0, im = 0;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
@@ -120,3 +121,4 @@ void EventDispatcher::handle_spectrum() {
|
||||
const UpdateSpectrumMessage message;
|
||||
baseband_processor->on_message(&message);
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,11 @@
|
||||
* @brief Enables the SDC subsystem.
|
||||
*/
|
||||
#if !defined(HAL_USE_SDC) || defined(__DOXYGEN__)
|
||||
#if defined(BASEBAND_flash_utility) || defined(BASEBAND_sd_over_usb)
|
||||
#define HAL_USE_SDC TRUE
|
||||
#else
|
||||
#define HAL_USE_SDC FALSE
|
||||
#endif /* BASEBAND_flash_utility */
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,6 +87,7 @@ void ERTProcessor::consume_symbol(
|
||||
) {
|
||||
const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0;
|
||||
scm_builder.execute(sliced_symbol);
|
||||
scmplus_builder.execute(sliced_symbol);
|
||||
idm_builder.execute(sliced_symbol);
|
||||
}
|
||||
|
||||
@@ -97,6 +98,13 @@ void ERTProcessor::scm_handler(
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void ERTProcessor::scmplus_handler(
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
const ERTPacketMessage message { ert::Packet::Type::SCMPLUS, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void ERTProcessor::idm_handler(
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
|
||||
@@ -44,10 +44,14 @@ constexpr uint64_t scm_preamble_and_sync_manchester { 0b101010101001011001100110
|
||||
constexpr size_t scm_preamble_and_sync_length { 42 - 10 };
|
||||
constexpr size_t scm_payload_length_max { 150 };
|
||||
|
||||
// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x16a3)[2:].zfill(16))])
|
||||
constexpr uint64_t scmplus_preamble_and_sync_manchester { 0b01010110011010011001100101011010 };
|
||||
constexpr size_t scmplus_preamble_and_sync_length { 32 - 0 };
|
||||
constexpr size_t scmplus_payload_length_max { 224 };
|
||||
|
||||
// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x555516a3)[2:].zfill(32))])
|
||||
constexpr uint64_t idm_preamble_and_sync_manchester { 0b0110011001100110011001100110011001010110011010011001100101011010 };
|
||||
constexpr size_t idm_preamble_and_sync_length { 64 - 16 };
|
||||
|
||||
constexpr size_t idm_payload_length_max { 1408 };
|
||||
|
||||
class ERTProcessor : public BasebandProcessor {
|
||||
@@ -80,6 +84,15 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> scmplus_builder {
|
||||
{ scmplus_preamble_and_sync_manchester, scmplus_preamble_and_sync_length, 1 },
|
||||
{ },
|
||||
{ scmplus_payload_length_max },
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->scmplus_handler(packet);
|
||||
}
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> idm_builder {
|
||||
{ idm_preamble_and_sync_manchester, idm_preamble_and_sync_length, 1 },
|
||||
{ },
|
||||
@@ -91,6 +104,7 @@ private:
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void scm_handler(const baseband::Packet& packet);
|
||||
void scmplus_handler(const baseband::Packet& packet);
|
||||
void idm_handler(const baseband::Packet& packet);
|
||||
|
||||
float sum_half_period[2];
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
#include "ff.h"
|
||||
|
||||
#include "w25q80bv.hpp"
|
||||
#include "debug.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#define PAGE_LEN 256U
|
||||
#define NUM_PAGES 4096U
|
||||
|
||||
void initialize_flash();
|
||||
void erase_flash();
|
||||
void initialize_sdcard();
|
||||
void write_firmware(FIL *);
|
||||
void write_page(size_t, uint8_t *, size_t);
|
||||
|
||||
int main() {
|
||||
const TCHAR *filename = reinterpret_cast<const TCHAR *>(&shared_memory.bb_data.data[0]);
|
||||
|
||||
initialize_flash();
|
||||
palSetPad(LED_PORT, LEDRX_PAD);
|
||||
erase_flash();
|
||||
|
||||
initialize_sdcard();
|
||||
|
||||
FIL firmware_file;
|
||||
if (f_open(&firmware_file, filename, FA_READ) != FR_OK) chDbgPanic("no file");
|
||||
|
||||
palSetPad(LED_PORT, LEDTX_PAD);
|
||||
|
||||
write_firmware(&firmware_file);
|
||||
|
||||
palClearPad(LED_PORT, LEDTX_PAD);
|
||||
palClearPad(LED_PORT, LEDRX_PAD);
|
||||
|
||||
f_close(&firmware_file);
|
||||
|
||||
while(1)
|
||||
__WFE();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void initialize_flash() {
|
||||
w25q80bv::disable_spifi();
|
||||
w25q80bv::initialite_spi();
|
||||
w25q80bv::setup();
|
||||
|
||||
w25q80bv::wait_for_device();
|
||||
w25q80bv::wait_not_busy();
|
||||
}
|
||||
|
||||
void erase_flash() {
|
||||
w25q80bv::remove_write_protection();
|
||||
w25q80bv::wait_not_busy();
|
||||
|
||||
w25q80bv::erase_chip();
|
||||
w25q80bv::wait_not_busy();
|
||||
}
|
||||
|
||||
void initialize_sdcard() {
|
||||
static FATFS fs;
|
||||
|
||||
sdcStart(&SDCD1, nullptr);
|
||||
if (sdcConnect(&SDCD1) == CH_FAILED) chDbgPanic("no sd card #1");
|
||||
if (f_mount(&fs, reinterpret_cast<const TCHAR*>(_T("")), 1) != FR_OK) chDbgPanic("no sd card #2");
|
||||
}
|
||||
|
||||
void write_firmware(FIL *firmware_file) {
|
||||
uint8_t *data_buffer = &shared_memory.bb_data.data[0];
|
||||
|
||||
for (size_t page_index = 0; page_index < NUM_PAGES; page_index++) {
|
||||
if (page_index % 32 == 0)
|
||||
palTogglePad(LED_PORT, LEDTX_PAD);
|
||||
|
||||
size_t bytes_read;
|
||||
if (f_read(firmware_file, data_buffer, PAGE_LEN, &bytes_read) != FR_OK) chDbgPanic("no data");
|
||||
|
||||
if (bytes_read > 0)
|
||||
write_page(page_index, data_buffer, bytes_read);
|
||||
|
||||
if (bytes_read < PAGE_LEN)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void write_page(size_t page_index, uint8_t *data_buffer, size_t data_length) {
|
||||
w25q80bv::wait_not_busy();
|
||||
w25q80bv::remove_write_protection();
|
||||
w25q80bv::wait_not_busy();
|
||||
w25q80bv::write(page_index, data_buffer, data_length);
|
||||
w25q80bv::wait_not_busy();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// Sine
|
||||
sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]);
|
||||
} else if (tone_shape == 2) {
|
||||
// Tri
|
||||
// Triangle
|
||||
int8_t a = (tone_phase & 0xFF000000) >> 24;
|
||||
sample = (a & 0x80) ? ((a << 1) ^ 0xFF) - 0x80 : (a << 1) + 0x80;
|
||||
} else if (tone_shape == 3) {
|
||||
@@ -61,24 +61,40 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
} else if (tone_shape == 5) {
|
||||
// Square
|
||||
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
|
||||
} else if (tone_shape == 6) {
|
||||
// Noise
|
||||
sample = (lfsr & 0xFF000000) >> 24;
|
||||
feedback = ((lfsr >> 31) ^ (lfsr >> 29) ^ (lfsr >> 15) ^ (lfsr >> 11)) & 1;
|
||||
lfsr = (lfsr << 1) | feedback;
|
||||
if (!lfsr) lfsr = 0x1337; // Shouldn't do this :(
|
||||
} else if (tone_shape == 6) {
|
||||
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
|
||||
// Periode 65535= 2^n-1, quite continuous .
|
||||
if (counter == 0) { // we slow down the shift register, because the pseudo random noise clock freq was too high for modulator.
|
||||
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ (lfsr_16 >> 12) & 1);
|
||||
lfsr_16 = (lfsr_16 >> 1) | (bit_16 << 15);
|
||||
sample = (lfsr_16 & 0x00FF); // main pseudo random noise generator.
|
||||
}
|
||||
if (counter == 5) { // after many empiric test, that combination mix of >>4 and >>5, gives a reasonable trade off white noise / good rf power level .
|
||||
sample = ((lfsr_16 & 0b0000111111110000) >> 4); // just changing the spectrum shape .
|
||||
}
|
||||
if (counter == 10) {
|
||||
sample = ((lfsr_16 & 0b0001111111100000) >> 5); // just changing the spectrum shape .
|
||||
}
|
||||
counter++;
|
||||
if (counter ==15) {
|
||||
counter=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (tone_shape < 6) { // we are in periodic signals, we need tone phases update.
|
||||
tone_phase += tone_delta;
|
||||
}
|
||||
|
||||
tone_phase += tone_delta;
|
||||
|
||||
// Do FM
|
||||
// Do FM modulation
|
||||
delta = sample * fm_delta;
|
||||
|
||||
phase += delta;
|
||||
sphase = phase + (64 << 24);
|
||||
|
||||
re = (sine_table_i8[(sphase & 0xFF000000) >> 24]);
|
||||
im = (sine_table_i8[(phase & 0xFF000000) >> 24]);
|
||||
im = (sine_table_i8[( phase & 0xFF000000) >> 24]);
|
||||
}
|
||||
|
||||
buffer.p[i] = {re, im};
|
||||
@@ -104,7 +120,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
|
||||
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
|
||||
tone_shape = message.shape;
|
||||
|
||||
lfsr = 0x54DF0119;
|
||||
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
|
||||
lfsr_16 = seed_value_16; // init lfsr 16 bits.
|
||||
|
||||
configured = true;
|
||||
break;
|
||||
|
||||
@@ -38,14 +38,18 @@ private:
|
||||
|
||||
BasebandThread baseband_thread { 1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
||||
|
||||
uint32_t tone_delta { 0 }, fm_delta { };
|
||||
uint32_t lfsr { }, feedback { }, tone_shape { };
|
||||
uint32_t tone_delta { 0 }, fm_delta { },tone_phase { 0 };
|
||||
uint8_t tone_shape { };
|
||||
uint32_t sample_count { 0 };
|
||||
bool auto_off { };
|
||||
uint32_t tone_phase { 0 }, phase { 0 }, delta { 0 }, sphase { 0 };
|
||||
int8_t sample { 0 };
|
||||
int8_t re { 0 }, im { 0 };
|
||||
|
||||
int32_t phase { 0 }, sphase { 0 }, delta { 0 }; // they may have sign in the pseudo random sample generation.
|
||||
int8_t sample { 0 }, re { 0 }, im { 0 }; // they have sign + and -.
|
||||
uint16_t seed_value_16 = {0xACE1}; // seed 16 bits lfsr : any nonzero start state will work.
|
||||
uint16_t lfsr_16 { }, bit_16 { }; // bit must be 16-bit to allow bit<<15 later in the code */
|
||||
uint8_t counter {0};
|
||||
// uint8_t seed_value = {0x56}; // Finally not used lfsr of 8 bits , seed 8blfsr : any nonzero start state will work.
|
||||
// uint8_t lfsr { }, bit { }; // Finally not used lfsr of 8 bits , bit must be 8-bit to allow bit<<7 later in the code */
|
||||
|
||||
TXProgressMessage txprogress_message { };
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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 "diskio.h"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
uint32_t get_capacity(void) {
|
||||
return mmcsdGetCardCapacity(&SDCD1);
|
||||
}
|
||||
|
||||
bool_t read_block(uint32_t startblk, uint8_t *buf, uint32_t n) {
|
||||
return sdcRead(&SDCD1, startblk, buf, n);
|
||||
}
|
||||
|
||||
bool_t write_block(uint32_t startblk, uint8_t *buf, uint32_t n) {
|
||||
return sdcWrite(&SDCD1, startblk, buf, n);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __DISKIO_H__
|
||||
#define __DISKIO_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <chtypes.h>
|
||||
|
||||
uint32_t get_capacity(void);
|
||||
bool_t read_block(uint32_t startblk, uint8_t *buf, uint32_t n);
|
||||
bool_t write_block(uint32_t startblk, uint8_t *buf, uint32_t n);
|
||||
|
||||
#endif /* __DISKIO_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user