diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 86b6f3fdda..721bc2799b 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -260,6 +260,7 @@ set(CPPSRC apps/lge_app.cpp apps/pocsag_app.cpp apps/replay_app.cpp + apps/gps_sim_app.cpp apps/soundboard_app.cpp apps/tpms_app.cpp protocols/aprs.cpp diff --git a/firmware/application/apps/gps_sim_app.cpp b/firmware/application/apps/gps_sim_app.cpp new file mode 100644 index 0000000000..83f721aae0 --- /dev/null +++ b/firmware/application/apps/gps_sim_app.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2020 Shao + * + * 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 "gps_sim_app.hpp" +#include "string_format.hpp" + +#include "ui_fileman.hpp" +#include "io_file.hpp" + +#include "baseband_api.hpp" +#include "portapack.hpp" +#include "portapack_persistent_memory.hpp" + +using namespace portapack; + +namespace ui { + +void GpsSimAppView::set_ready() { + ready_signal = true; +} + +void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) { + File data_file, info_file; + char file_data[257]; + + // Get file size + auto data_open_error = data_file.open("/" + new_file_path.string()); + if (data_open_error.is_valid()) { + file_error(); + return; + } + + file_path = new_file_path; + + // Get original record frequency if available + std::filesystem::path info_file_path = file_path; + info_file_path.replace_extension(u".TXT"); + + sample_rate = 500000; + + auto info_open_error = info_file.open("/" + info_file_path.string()); + if (!info_open_error.is_valid()) { + memset(file_data, 0, 257); + auto read_size = info_file.read(file_data, 256); + if (!read_size.is_error()) { + auto pos1 = strstr(file_data, "center_frequency="); + if (pos1) { + pos1 += 17; + field_frequency.set_value(strtoll(pos1, nullptr, 10)); + } + + auto pos2 = strstr(file_data, "sample_rate="); + if (pos2) { + pos2 += 12; + sample_rate = strtoll(pos2, nullptr, 10); + } + } + } + + text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz"); + + auto file_size = data_file.size(); + auto duration = (file_size * 1000) / (1 * 2 * sample_rate); + + progressbar.set_max(file_size); + text_filename.set(file_path.filename().string().substr(0, 12)); + text_duration.set(to_string_time_ms(duration)); + + button_play.focus(); +} + +void GpsSimAppView::on_tx_progress(const uint32_t progress) { + progressbar.set_value(progress); +} + +void GpsSimAppView::focus() { + button_open.focus(); +} + +void GpsSimAppView::file_error() { + nav_.display_modal("Error", "File read error."); +} + +bool GpsSimAppView::is_active() const { + return (bool)replay_thread; +} + +void GpsSimAppView::toggle() { + if( is_active() ) { + stop(false); + } else { + start(); + } +} + +void GpsSimAppView::start() { + stop(false); + + std::unique_ptr reader; + + auto p = std::make_unique(); + auto open_error = p->open(file_path); + if( open_error.is_valid() ) { + file_error(); + } else { + reader = std::move(p); + } + + if( reader ) { + button_play.set_bitmap(&bitmap_stop); + baseband::set_sample_rate(sample_rate ); + + replay_thread = std::make_unique( + std::move(reader), + read_size, buffer_count, + &ready_signal, + [](uint32_t return_code) { + ReplayThreadDoneMessage message { return_code }; + EventDispatcher::send_message(message); + } + ); + } + + radio::enable({ + receiver_model.tuning_frequency(), + sample_rate , + baseband_bandwidth, + rf::Direction::Transmit, + receiver_model.rf_amp(), + static_cast(receiver_model.lna()), + static_cast(receiver_model.vga()) + }); +} + +void GpsSimAppView::stop(const bool do_loop) { + if( is_active() ) + replay_thread.reset(); + + if (do_loop && check_loop.value()) { + start(); + } else { + radio::disable(); + button_play.set_bitmap(&bitmap_play); + } + + ready_signal = false; +} + +void GpsSimAppView::handle_replay_thread_done(const uint32_t return_code) { + if (return_code == ReplayThread::END_OF_FILE) { + stop(true); + } else if (return_code == ReplayThread::READ_ERROR) { + stop(false); + file_error(); + } + + progressbar.set_value(0); +} + +GpsSimAppView::GpsSimAppView( + NavigationView& nav +) : nav_ (nav) +{ + baseband::run_image(portapack::spi_flash::image_tag_gps); + + add_children({ + &labels, + &button_open, + &text_filename, + &text_sample_rate, + &text_duration, + &progressbar, + &field_frequency, + &field_lna, + &field_rf_amp, + &check_loop, + &button_play, + &waterfall, + }); + + field_frequency.set_value(target_frequency()); + field_frequency.set_step(receiver_model.frequency_step()); + field_frequency.on_change = [this](rf::Frequency f) { + this->on_target_frequency_changed(f); + }; + field_frequency.on_edit = [this, &nav]() { + // TODO: Provide separate modal method/scheme? + auto new_view = nav.push(this->target_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + this->on_target_frequency_changed(f); + this->field_frequency.set_value(f); + }; + }; + + field_frequency.set_step(5000); + + button_play.on_select = [this](ImageButton&) { + this->toggle(); + }; + + button_open.on_select = [this, &nav](Button&) { + auto open_view = nav.push(".C8"); + open_view->on_changed = [this](std::filesystem::path new_file_path) { + on_file_changed(new_file_path); + }; + }; +} + +GpsSimAppView::~GpsSimAppView() { + radio::disable(); + baseband::shutdown(); +} + +void GpsSimAppView::on_hide() { + // TODO: Terrible kludge because widget system doesn't notify Waterfall that + // it's being shown or hidden. + waterfall.on_hide(); + View::on_hide(); +} + +void GpsSimAppView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + + const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height }; + waterfall.set_parent_rect(waterfall_rect); +} + +void GpsSimAppView::on_target_frequency_changed(rf::Frequency f) { + set_target_frequency(f); +} + +void GpsSimAppView::set_target_frequency(const rf::Frequency new_value) { + persistent_memory::set_tuned_frequency(new_value);; +} + +rf::Frequency GpsSimAppView::target_frequency() const { + return persistent_memory::tuned_frequency(); +} + +} /* namespace ui */ diff --git a/firmware/application/apps/gps_sim_app.hpp b/firmware/application/apps/gps_sim_app.hpp new file mode 100644 index 0000000000..4f26c11a6d --- /dev/null +++ b/firmware/application/apps/gps_sim_app.hpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2020 Shao + * + * 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 __GPS_SIM_APP_HPP__ +#define __GPS_SIM_APP_HPP__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "replay_thread.hpp" +#include "ui_spectrum.hpp" + +#include +#include + +namespace ui { + +class GpsSimAppView : public View { +public: + GpsSimAppView(NavigationView& nav); + ~GpsSimAppView(); + + void on_hide() override; + void set_parent_rect(const Rect new_parent_rect) override; + void focus() override; + + std::string title() const override { return "GPS Simulator"; }; + +private: + NavigationView& nav_; + + static constexpr ui::Dim header_height = 3 * 16; + + uint32_t sample_rate = 0; + static constexpr uint32_t baseband_bandwidth = 3000000; //filter bandwidth + const size_t read_size { 16384 }; + const size_t buffer_count { 3 }; + + void on_file_changed(std::filesystem::path new_file_path); + void on_target_frequency_changed(rf::Frequency f); + void on_tx_progress(const uint32_t progress); + + void set_target_frequency(const rf::Frequency new_value); + rf::Frequency target_frequency() const; + + void toggle(); + void start(); + void stop(const bool do_loop); + bool is_active() const; + void set_ready(); + void handle_replay_thread_done(const uint32_t return_code); + void file_error(); + + std::filesystem::path file_path { }; + std::unique_ptr replay_thread { }; + bool ready_signal { false }; + + Labels labels { + { { 10 * 8, 2 * 16 }, "LNA: A:", Color::light_grey() } + }; + + Button button_open { + { 0 * 8, 0 * 16, 10 * 8, 2 * 16 }, + "Open file" + }; + + Text text_filename { + { 11 * 8, 0 * 16, 12 * 8, 16 }, + "-" + }; + Text text_sample_rate { + { 24 * 8, 0 * 16, 6 * 8, 16 }, + "-" + }; + + Text text_duration { + { 11 * 8, 1 * 16, 6 * 8, 16 }, + "-" + }; + ProgressBar progressbar { + { 18 * 8, 1 * 16, 12 * 8, 16 } + }; + + FrequencyField field_frequency { + { 0 * 8, 2 * 16 }, + }; + LNAGainField field_lna { + { 14 * 8, 2 * 16 } + }; + RFAmpField field_rf_amp { + { 19 * 8, 2 * 16 } + }; + Checkbox check_loop { + { 21 * 8, 2 * 16 }, + 4, + "Loop", + true + }; + ImageButton button_play { + { 28 * 8, 2 * 16, 2 * 8, 1 * 16 }, + &bitmap_play, + Color::green(), + Color::black() + }; + + spectrum::WaterfallWidget waterfall { }; + + MessageHandlerRegistration message_handler_replay_thread_error { + Message::ID::ReplayThreadDone, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->handle_replay_thread_done(message.return_code); + } + }; + + MessageHandlerRegistration message_handler_fifo_signal { + Message::ID::RequestSignal, + [this](const Message* const p) { + const auto message = static_cast(p); + if (message->signal == RequestSignalMessage::Signal::FillRequest) { + this->set_ready(); + } + } + }; + + MessageHandlerRegistration message_handler_tx_progress { + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress); + } + }; +}; + +} /* namespace ui */ + +#endif/*__GPS_SIM_APP_HPP__*/ diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index 9626bedecc..b837d3d2f3 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -1503,6 +1503,28 @@ static constexpr Bitmap bitmap_icon_replay { { 16, 16 }, bitmap_icon_replay_data }; +static constexpr uint8_t bitmap_gps_sim_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0xF0, 0x0F, + 0x4C, 0x32, + 0xFE, 0x7F, + 0x25, 0xA4, + 0x25, 0xA4, + 0xFF, 0xFF, + 0x25, 0xA4, + 0x25, 0xA4, + 0xFE, 0x7F, + 0x4C, 0x32, + 0xF0, 0x0F, + 0x00, 0x00, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_gps_sim { + { 16, 16 }, bitmap_gps_sim_data +}; + static constexpr uint8_t bitmap_icon_btle_data[] = { 0x00, 0x00, 0x80, 0x00, diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index bd1eb0aa85..7eea7a7634 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -76,6 +76,7 @@ #include "lge_app.hpp" #include "pocsag_app.hpp" #include "replay_app.hpp" +#include "gps_sim_app.hpp" #include "soundboard_app.hpp" #include "tpms_app.hpp" @@ -377,6 +378,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) { { "ADS-B [S]", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push(); } }, { "APRS", ui::Color::orange(), &bitmap_icon_aprs, [&nav](){ nav.push(); } }, { "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push(); } }, + { "GPS Sim", ui::Color::yellow(), &bitmap_gps_sim, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::yellow(), &bitmap_icon_jammer, [&nav](){ nav.push(); } }, { "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push(); } }, { "LGE tool", ui::Color::yellow(), &bitmap_icon_lge, [&nav](){ nav.push(); } }, diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 028e62e408..c4aafa564e 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -437,6 +437,13 @@ set(MODE_CPPSRC ) DeclareTargets(PREP replay) +### GPS Simulator + +set(MODE_CPPSRC + proc_gps_sim.cpp +) +DeclareTargets(PGPS gps_sim) + ### Signal generator set(MODE_CPPSRC diff --git a/firmware/baseband/proc_gps_sim.cpp b/firmware/baseband/proc_gps_sim.cpp new file mode 100644 index 0000000000..230c0bda75 --- /dev/null +++ b/firmware/baseband/proc_gps_sim.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2020 Shao + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_gps_sim.hpp" +#include "sine_table_int8.hpp" +#include "portapack_shared_memory.hpp" + +#include "event_m4.hpp" + +#include "utility.hpp" + +ReplayProcessor::ReplayProcessor() { + channel_filter_pass_f = taps_200k_decim_1.pass_frequency_normalized * 1000000; // 162760.416666667 + channel_filter_stop_f = taps_200k_decim_1.stop_frequency_normalized * 1000000; // 337239.583333333 + + spectrum_samples = 0; + + channel_spectrum.set_decimation_factor(1); + + configured = false; +} + +void ReplayProcessor::execute(const buffer_c8_t& buffer) { + /* 4MHz, 2048 samples */ + + if (!configured) return; + + // File data is in C16 format, we need C8 + // File samplerate is 500kHz, we're at 4MHz + // iq_buffer can only be 512 C16 samples (RAM limitation) + // To fill up the 2048-sample C8 buffer, we need: + // 2048 samples * 2 bytes per sample = 4096 bytes + // Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each + // So 256 * 4 bytes per sample (C16) = 1024 bytes from the file + if( stream ) { //sizeof(*buffer.p) = sizeof(C8) = 2*int8 = 2 bytes //buffer.count = 2048 + const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count ); // *2 (C16), /8 (oversampling) should be == 1024 + bytes_read += stream->read(iq_buffer.p, bytes_to_read); + } + + // Fill and "stretch" + for (size_t i = 0; i < buffer.count; i++) { + /*if (i & 3) { + buffer.p[i] = buffer.p[i - 1]; + } else { + auto re_out = iq_buffer.p[i >> 3].real() ; + auto im_out = iq_buffer.p[i >> 3].imag() ; + buffer.p[i] = { (int8_t)re_out, (int8_t)im_out }; + }*/ + /* + if (i % 8 != 0) { + buffer.p[i] = buffer.p[i - 1]; + } else { + auto re_out = iq_buffer.p[i/8].real() ; + auto im_out = iq_buffer.p[i/8].imag() ; + buffer.p[i] = { (int8_t)re_out, (int8_t)im_out }; + }*/ + + auto re_out = iq_buffer.p[i].real() ; + auto im_out = iq_buffer.p[i].imag() ; + buffer.p[i] = { (int8_t)re_out, (int8_t)im_out }; + } + + spectrum_samples += buffer.count; + if( spectrum_samples >= spectrum_interval_samples ) { + spectrum_samples -= spectrum_interval_samples; + //channel_spectrum.feed(iq_buffer, channel_filter_pass_f, channel_filter_stop_f); + + txprogress_message.progress = bytes_read; // Inform UI about progress + txprogress_message.done = false; + shared_memory.application_queue.push(txprogress_message); + } +} + +void ReplayProcessor::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + case Message::ID::SamplerateConfig: + samplerate_config(*reinterpret_cast(message)); + break; + + case Message::ID::ReplayConfig: + configured = false; + bytes_read = 0; + replay_config(*reinterpret_cast(message)); + break; + + // App has prefilled the buffers, we're ready to go now + case Message::ID::FIFOData: + configured = true; + break; + + default: + break; + } +} + +void ReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) { + baseband_fs = message.sample_rate; + baseband_thread.set_sampling_rate(baseband_fs); + spectrum_interval_samples = baseband_fs / spectrum_rate_hz; +} + +void ReplayProcessor::replay_config(const ReplayConfigMessage& message) { + if( message.config ) { + + stream = std::make_unique(message.config); + + // Tell application that the buffers and FIFO pointers are ready, prefill + shared_memory.application_queue.push(sig_message); + } else { + stream.reset(); + } +} + +int main() { + EventDispatcher event_dispatcher { std::make_unique() }; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_gps_sim.hpp b/firmware/baseband/proc_gps_sim.hpp new file mode 100644 index 0000000000..440e79de52 --- /dev/null +++ b/firmware/baseband/proc_gps_sim.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2020 Shao + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_GPS_SIM_HPP__ +#define __PROC_GPS_SIM_HPP__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" + +#include "spectrum_collector.hpp" + +#include "stream_output.hpp" + +#include +#include + +class ReplayProcessor : public BasebandProcessor { +public: + ReplayProcessor(); + + void execute(const buffer_c8_t& buffer) override; + + void on_message(const Message* const message) override; + +private: + size_t baseband_fs = 0; + static constexpr auto spectrum_rate_hz = 50.0f; + + BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit }; + + std::array iq { }; + const buffer_c8_t iq_buffer { + iq.data(), + iq.size(), + baseband_fs + }; + + uint32_t channel_filter_pass_f = 0; + uint32_t channel_filter_stop_f = 0; + + std::unique_ptr stream { }; + + SpectrumCollector channel_spectrum { }; + size_t spectrum_interval_samples = 0; + size_t spectrum_samples = 0; + + bool configured { false }; + uint32_t bytes_read { 0 }; + + void samplerate_config(const SamplerateConfigMessage& message); + void replay_config(const ReplayConfigMessage& message); + + TXProgressMessage txprogress_message { }; + RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest }; +}; + +#endif/*__PROC_GPS_SIM_HPP__*/ diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 023c1317d3..896340be5a 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -92,6 +92,7 @@ constexpr image_tag_t image_tag_mic_tx { 'P', 'M', 'T', 'X' }; constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' }; constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' }; constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' }; +constexpr image_tag_t image_tag_gps { 'P', 'G', 'P', 'S' }; constexpr image_tag_t image_tag_siggen { 'P', 'S', 'I', 'G' }; constexpr image_tag_t image_tag_sstv_tx { 'P', 'S', 'T', 'X' }; constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' };