diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index faa3140b..013d9278 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -172,6 +172,7 @@ set(CPPSRC ui_rds.cpp ui_receiver.cpp ui_record_view.cpp + ui_replay_view.cpp ui_rssi.cpp ui_sd_card_status_view.cpp # ui_sd_card_debug.cpp @@ -198,6 +199,7 @@ set(CPPSRC ert_app.cpp ${COMMON}/ert_packet.cpp capture_app.cpp + replay_app.cpp sd_card.cpp time.cpp file.cpp @@ -206,6 +208,7 @@ set(CPPSRC log_file.cpp ${COMMON}/png_writer.cpp capture_thread.cpp + replay_thread.cpp ${COMMON}/manchester.cpp string_format.cpp temperature_logger.cpp diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 09da00dd..fbb2a2a2 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -3731,6 +3731,60 @@ recent_entries.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/recent_entries.cpp.s .PHONY : recent_entries.cpp.s +replay_app.obj: replay_app.cpp.obj + +.PHONY : replay_app.obj + +# target to build an object file +replay_app.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_app.cpp.obj +.PHONY : replay_app.cpp.obj + +replay_app.i: replay_app.cpp.i + +.PHONY : replay_app.i + +# target to preprocess a source file +replay_app.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_app.cpp.i +.PHONY : replay_app.cpp.i + +replay_app.s: replay_app.cpp.s + +.PHONY : replay_app.s + +# target to generate assembly for a file +replay_app.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_app.cpp.s +.PHONY : replay_app.cpp.s + +replay_thread.obj: replay_thread.cpp.obj + +.PHONY : replay_thread.obj + +# target to build an object file +replay_thread.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_thread.cpp.obj +.PHONY : replay_thread.cpp.obj + +replay_thread.i: replay_thread.cpp.i + +.PHONY : replay_thread.i + +# target to preprocess a source file +replay_thread.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_thread.cpp.i +.PHONY : replay_thread.cpp.i + +replay_thread.s: replay_thread.cpp.s + +.PHONY : replay_thread.s + +# target to generate assembly for a file +replay_thread.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/replay_thread.cpp.s +.PHONY : replay_thread.cpp.s + rf_path.obj: rf_path.cpp.obj .PHONY : rf_path.obj @@ -4784,6 +4838,33 @@ ui_record_view.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_record_view.cpp.s .PHONY : ui_record_view.cpp.s +ui_replay_view.obj: ui_replay_view.cpp.obj + +.PHONY : ui_replay_view.obj + +# target to build an object file +ui_replay_view.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_replay_view.cpp.obj +.PHONY : ui_replay_view.cpp.obj + +ui_replay_view.i: ui_replay_view.cpp.i + +.PHONY : ui_replay_view.i + +# target to preprocess a source file +ui_replay_view.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_replay_view.cpp.i +.PHONY : ui_replay_view.cpp.i + +ui_replay_view.s: ui_replay_view.cpp.s + +.PHONY : ui_replay_view.s + +# target to generate assembly for a file +ui_replay_view.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_replay_view.cpp.s +.PHONY : ui_replay_view.cpp.s + ui_rssi.obj: ui_rssi.cpp.obj .PHONY : ui_rssi.obj @@ -5463,6 +5544,12 @@ help: @echo "... recent_entries.obj" @echo "... recent_entries.i" @echo "... recent_entries.s" + @echo "... replay_app.obj" + @echo "... replay_app.i" + @echo "... replay_app.s" + @echo "... replay_thread.obj" + @echo "... replay_thread.i" + @echo "... replay_thread.s" @echo "... rf_path.obj" @echo "... rf_path.i" @echo "... rf_path.s" @@ -5580,6 +5667,9 @@ help: @echo "... ui_record_view.obj" @echo "... ui_record_view.i" @echo "... ui_record_view.s" + @echo "... ui_replay_view.obj" + @echo "... ui_replay_view.i" + @echo "... ui_replay_view.s" @echo "... ui_rssi.obj" @echo "... ui_rssi.i" @echo "... ui_rssi.s" diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index b6bdef75..9d6816cd 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -512,6 +512,29 @@ static constexpr Bitmap bitmap_icon_remote { { 16, 16 }, bitmap_icon_remote_data }; +static constexpr uint8_t bitmap_icon_replay_data[] = { + 0x00, 0x00, + 0xC0, 0x07, + 0xF0, 0x1F, + 0x79, 0x3C, + 0x1D, 0x70, + 0x0F, 0x60, + 0x07, 0xE0, + 0x1F, 0xC0, + 0x00, 0xC0, + 0x00, 0xE0, + 0x00, 0x60, + 0x00, 0x70, + 0x30, 0x3C, + 0xE0, 0x0F, + 0x80, 0x03, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_icon_replay { + { 16, 16 }, bitmap_icon_replay_data +}; + static constexpr uint8_t bitmap_icon_soundboard_data[] = { 0x00, 0x00, 0xDE, 0x7B, diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 47f5fcbc..38998119 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -26,8 +26,8 @@ //TEST: Imperial in whipcalc //TEST: Numbers //TEST: Jammer -//TODO: Frequency manager auto-remove duplicates +//TODO: Frequency manager auto-remove duplicates //TODO: "TX box" view or composite widget with frequency and bw settings, simple and advanced setup TX buttons... //TODO: Morse coder for foxhunts //TODO: Finish EPAR tx diff --git a/firmware/application/replay_app.cpp b/firmware/application/replay_app.cpp new file mode 100644 index 00000000..d16a0ad2 --- /dev/null +++ b/firmware/application/replay_app.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "replay_app.hpp" + +#include "baseband_api.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include "portapack_persistent_memory.hpp" +using namespace portapack; + +namespace ui { + +ReplayAppView::ReplayAppView(NavigationView& nav) { + baseband::run_image(portapack::spi_flash::image_tag_replay); + + add_children({ { + &channel, + &field_frequency, + &field_frequency_step, + &field_rf_amp, + &field_lna, + &field_vga, + &replay_view, + &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_step.set_by_value(receiver_model.frequency_step()); + field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) { + receiver_model.set_frequency_step(v); + this->field_frequency.set_step(v); + }; + + radio::enable({ + target_frequency(), + sampling_rate, + baseband_bandwidth, + rf::Direction::Transmit, + receiver_model.rf_amp(), + static_cast(receiver_model.lna()), + static_cast(receiver_model.vga()), + 1, + }); + + replay_view.set_sampling_rate(sampling_rate / 8); + replay_view.on_error = [&nav](std::string message) { + nav.display_modal("Error", message); + }; +} + +ReplayAppView::~ReplayAppView() { + radio::disable(); + baseband::shutdown(); +} + +void ReplayAppView::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 ReplayAppView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + + const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), static_cast(new_parent_rect.height() - header_height) }; + waterfall.set_parent_rect(waterfall_rect); +} + +void ReplayAppView::focus() { + field_frequency.focus(); +} + +void ReplayAppView::on_target_frequency_changed(rf::Frequency f) { + set_target_frequency(f); +} + +void ReplayAppView::set_target_frequency(const rf::Frequency new_value) { + persistent_memory::set_tuned_frequency(new_value);; +} + +rf::Frequency ReplayAppView::target_frequency() const { + return persistent_memory::tuned_frequency(); +} + +} /* namespace ui */ diff --git a/firmware/application/replay_app.hpp b/firmware/application/replay_app.hpp new file mode 100644 index 00000000..8d86e513 --- /dev/null +++ b/firmware/application/replay_app.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __REPLAY_APP_HPP__ +#define __REPLAY_APP_HPP__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "ui_replay_view.hpp" +#include "ui_spectrum.hpp" + +#include +#include + +namespace ui { + +class ReplayAppView : public View { +public: + ReplayAppView(NavigationView& nav); + ~ReplayAppView(); + + void on_hide() override; + + void set_parent_rect(const Rect new_parent_rect) override; + + void focus() override; + + std::string title() const override { return "Capture"; }; + +private: + static constexpr ui::Dim header_height = 2 * 16; + + static constexpr uint32_t sampling_rate = 4000000; + static constexpr uint32_t baseband_bandwidth = 2500000; + + void on_target_frequency_changed(rf::Frequency f); + + rf::Frequency target_frequency() const; + void set_target_frequency(const rf::Frequency new_value); + + Channel channel { + { 24 * 8, 5, 6 * 8, 4 }, + }; + + FrequencyField field_frequency { + { 0 * 8, 0 * 16 }, + }; + + FrequencyStepView field_frequency_step { + { 10 * 8, 0 * 16 }, + }; + + RFAmpField field_rf_amp { + { 16 * 8, 0 * 16 } + }; + + LNAGainField field_lna { + { 18 * 8, 0 * 16 } + }; + + VGAGainField field_vga { + { 21 * 8, 0 * 16 } + }; + + ReplayView replay_view { + { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, + "BBD_????", ReplayView::FileType::RawS16, 16384, 3 + }; + + spectrum::WaterfallWidget waterfall; +}; + +} /* namespace ui */ + +#endif/*__REPLAY_APP_HPP__*/ diff --git a/firmware/application/replay_thread.cpp b/firmware/application/replay_thread.cpp new file mode 100644 index 00000000..8e04d12a --- /dev/null +++ b/firmware/application/replay_thread.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "replay_thread.hpp" + +#include "baseband_api.hpp" + +// StreamOutput /////////////////////////////////////////////////////////// + +class StreamOutput { +public: + StreamOutput(CaptureConfig* const config); + ~StreamOutput(); + + size_t available() { + return fifo_buffers_full->len(); + } + + StreamBuffer* get_buffer() { + StreamBuffer* p { nullptr }; + fifo_buffers_full->out(p); + return p; + } + + bool release_buffer(StreamBuffer* const p) { + p->empty(); + return fifo_buffers_empty->in(p); + } + + static FIFO* fifo_buffers_empty; + static FIFO* fifo_buffers_full; + +private: + CaptureConfig* const config; +}; + +FIFO* StreamOutput::fifo_buffers_empty = nullptr; +FIFO* StreamOutput::fifo_buffers_full = nullptr; + +StreamOutput::StreamOutput( + CaptureConfig* const config +) : config { config } +{ + baseband::capture_start(config); + fifo_buffers_empty = config->fifo_buffers_empty; + fifo_buffers_full = config->fifo_buffers_full; +} + +StreamOutput::~StreamOutput() { + fifo_buffers_full = nullptr; + fifo_buffers_empty = nullptr; + baseband::capture_stop(); +} + +// CaptureThread ////////////////////////////////////////////////////////// + +Thread* ReplayThread::thread = nullptr; + +ReplayThread::ReplayThread( + std::unique_ptr reader, + size_t read_size, + size_t buffer_count, + std::function success_callback, + std::function error_callback +) : config { read_size, buffer_count }, + reader { std::move(reader) }, + success_callback { std::move(success_callback) }, + error_callback { std::move(error_callback) } +{ + // Need significant stack for FATFS + thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ReplayThread::static_fn, this); +} + +ReplayThread::~ReplayThread() { + if( thread ) { + chThdTerminate(thread); + chEvtSignal(thread, event_mask_loop_wake); + chThdWait(thread); + thread = nullptr; + } +} + +void ReplayThread::check_fifo_isr() { + // TODO: Prevent over-signalling by transmitting a set of + // flags from the baseband core. + const auto fifo = StreamOutput::fifo_buffers_full; + if( fifo ) { + if( !fifo->is_empty() ) { + chEvtSignalI(thread, event_mask_loop_wake); + } + } +} + +msg_t ReplayThread::static_fn(void* arg) { + auto obj = static_cast(arg); + const auto error = obj->run(); + if( error.is_valid() && obj->error_callback ) { + obj->error_callback(error.value()); + } else { + if( obj->success_callback ) { + obj->success_callback(); + } + } + return 0; +} + +Optional ReplayThread::run() { + StreamOutput stream { &config }; + + while( !chThdShouldTerminate() ) { + if( stream.available() ) { + auto buffer = stream.get_buffer(); + auto read_result = reader->reader(buffer->data(), buffer->size()); + if( read_result.is_error() ) { + return read_result.error(); + } + stream.release_buffer(buffer); + } else { + chEvtWaitAny(event_mask_loop_wake); + } + } + + return { }; +} diff --git a/firmware/application/replay_thread.hpp b/firmware/application/replay_thread.hpp new file mode 100644 index 00000000..16b0e1ca --- /dev/null +++ b/firmware/application/replay_thread.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __REPLAY_THREAD_H__ +#define __REPLAY_THREAD_H__ + +#include "ch.h" + +#include "event_m0.hpp" + +#include "file.hpp" +#include "optional.hpp" + +#include +#include +#include + +class Reader { +public: + virtual File::Result read(const void* const buffer, const size_t bytes) = 0; + virtual ~Reader() = default; +}; + +class ReplayThread { +public: + ReplayThread( + std::unique_ptr reader, + size_t read_size, + size_t buffer_count, + std::function success_callback, + std::function error_callback + ); + ~ReplayThread(); + + const ReplayConfig& state() const { + return config; + } + + static void check_fifo_isr(); + +private: + static constexpr auto event_mask_loop_wake = EVENT_MASK(0); + + ReplayConfig config; + std::unique_ptr reader; + std::function success_callback; + std::function error_callback; + static Thread* thread; + + static msg_t static_fn(void* arg); + + Optional run(); +}; + +#endif/*__REPLAY_THREAD_H__*/ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index c76e7d4c..0b651257 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -55,6 +55,7 @@ #include "tpms_app.hpp" #include "pocsag_app.hpp" #include "capture_app.hpp" +#include "replay_app.hpp" #include "core_control.hpp" @@ -338,16 +339,16 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) { } SystemMenuView::SystemMenuView(NavigationView& nav) { - add_items<11>({ { + add_items<12>({ { { "Play dead", ui::Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push(); } }, { "Receivers", ui::Color::cyan(), &bitmap_icon_receiver, [&nav](){ nav.push(); } }, { "Capture", ui::Color::cyan(), &bitmap_icon_capture, [&nav](){ nav.push(); } }, + { "Replay", ui::Color::blue(), &bitmap_icon_replay, [&nav](){ nav.push(); } }, { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push(); } }, { "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push(); } }, { "Close Call", ui::Color::orange(),&bitmap_icon_closecall, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push(); } }, { "Utilities", ui::Color::purple(),nullptr, [&nav](){ nav.push(); } }, - //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, { "Setup", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, //{ "Debug", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, { "HackRF mode", ui::Color::white(), &bitmap_icon_hackrf, [this, &nav](){ hackrf_mode(nav); } }, diff --git a/firmware/application/ui_replay_view.cpp b/firmware/application/ui_replay_view.cpp new file mode 100644 index 00000000..52628f6e --- /dev/null +++ b/firmware/application/ui_replay_view.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_replay_view.hpp" + +#include "portapack.hpp" +#include "message.hpp" +#include "portapack_shared_memory.hpp" +using namespace portapack; + +#include "time.hpp" + +#include "string_format.hpp" +#include "utility.hpp" + +#include + +namespace ui { + +ReplayView::ReplayView( + const Rect parent_rect, + std::string filename_stem_pattern, + const FileType file_type, + const size_t read_size, + const size_t buffer_count +) : View { parent_rect }, + filename_stem_pattern { filename_stem_pattern }, + file_type { file_type }, + read_size { read_size }, + buffer_count { buffer_count } +{ + add_children({ { + &rect_background, + &button_record, + &text_replay_filename, + &text_time_seek, + } }); + + rect_background.set_parent_rect({ { 0, 0 }, size() }); + + button_record.on_select = [this](ImageButton&) { + this->toggle(); + }; + + signal_token_tick_second = time::signal_tick_second += [this]() { + this->on_tick_second(); + }; +} + +ReplayView::~ReplayView() { + time::signal_tick_second -= signal_token_tick_second; +} + +void ReplayView::focus() { + button_record.focus(); +} + +void ReplayView::set_sampling_rate(const size_t new_sampling_rate) { + if( new_sampling_rate != sampling_rate ) { + stop(); + sampling_rate = new_sampling_rate; + + button_record.hidden(sampling_rate == 0); + text_replay_filename.hidden(sampling_rate == 0); + text_time_seek.hidden(sampling_rate == 0); + rect_background.hidden(sampling_rate != 0); + + update_status_display(); + } +} + +bool ReplayView::is_active() const { + return (bool)replay_thread; +} + +void ReplayView::toggle() { + if( is_active() ) { + stop(); + } else { + start(); + } +} + +void ReplayView::start() { + stop(); + + text_replay_filename.set(""); + + if( sampling_rate == 0 ) { + return; + } + + const auto filename_stem = next_filename_stem_matching_pattern(filename_stem_pattern); + if( filename_stem.empty() ) { + return; + } + + std::unique_ptr reader; + auto p = std::make_unique(); + auto create_error = p->create( + filename_stem + ".C16" + ); + if( create_error.is_valid() ) { + handle_error(create_error.value()); + } else { + reader = std::move(p); + } + + if( reader ) { + text_replay_filename.set(filename_stem); + button_record.set_bitmap(&bitmap_stop); + replay_thread = std::make_unique( + std::move(reader), + read_size, buffer_count, + []() { + ReplayThreadDoneMessage message { }; + EventDispatcher::send_message(message); + }, + [](File::Error error) { + ReplayThreadDoneMessage message { error.code() }; + EventDispatcher::send_message(message); + } + ); + } + + update_status_display(); +} + +void ReplayView::stop() { + if( is_active() ) { + replay_thread.reset(); + button_record.set_bitmap(&bitmap_record); + } + + update_status_display(); +} + +void ReplayView::on_tick_second() { + update_status_display(); +} + +void ReplayView::update_status_display() { + /*if( sampling_rate ) { + const auto space_info = std::filesystem::space(""); + const uint32_t bytes_per_second = file_type == FileType::WAV ? (sampling_rate * 2) : (sampling_rate * 4); + const uint32_t available_seconds = space_info.free / bytes_per_second; + const uint32_t seconds = available_seconds % 60; + const uint32_t available_minutes = available_seconds / 60; + const uint32_t minutes = available_minutes % 60; + const uint32_t hours = available_minutes / 60; + const std::string available_time = + to_string_dec_uint(hours, 3, ' ') + ":" + + to_string_dec_uint(minutes, 2, '0') + ":" + + to_string_dec_uint(seconds, 2, '0'); + text_time_available.set(available_time); + }*/ +} + +void ReplayView::handle_replay_thread_done(const File::Error error) { + stop(); + if( error.code() ) { + handle_error(error); + } +} + +void ReplayView::handle_error(const File::Error error) { + if( on_error ) { + on_error(error.what()); + } +} + +} /* namespace ui */ diff --git a/firmware/application/ui_replay_view.hpp b/firmware/application/ui_replay_view.hpp new file mode 100644 index 00000000..102bea62 --- /dev/null +++ b/firmware/application/ui_replay_view.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_REPLAY_VIEW_H__ +#define __UI_REPLAY_VIEW_H__ + +#include "ui_widget.hpp" + +#include "replay_thread.hpp" +#include "signal.hpp" +#include "bitmap.hpp" + +#include +#include +#include + +namespace ui { + +class ReplayView : public View { +public: + std::function on_error; + + enum FileType { + RawS16 = 2, + WAV = 3, + }; + + ReplayView( + const Rect parent_rect, + std::string filename_stem_pattern, + FileType file_type, + const size_t read_size, + const size_t buffer_count + ); + ~ReplayView(); + + void focus() override; + + void set_sampling_rate(const size_t new_sampling_rate); + + void start(); + void stop(); + + bool is_active() const; + +private: + void toggle(); + + void on_tick_second(); + void update_status_display(); + + void handle_replay_thread_done(const File::Error error); + void handle_error(const File::Error error); + + bool pwmrssi_enabled = false; + const std::string filename_stem_pattern; + const FileType file_type; + const size_t read_size; + const size_t buffer_count; + size_t sampling_rate { 0 }; + SignalToken signal_token_tick_second; + + Rectangle rect_background { + Color::black() + }; + + ImageButton button_record { + { 4 * 8, 0 * 16, 2 * 8, 1 * 16 }, + &bitmap_record, + Color::red(), + Color::black() + }; + + Text text_replay_filename { + { 7 * 8, 0 * 16, 8 * 8, 16 }, + "", + }; + + Text text_time_seek { + { 21 * 8, 0 * 16, 9 * 8, 16 }, + "", + }; + + std::unique_ptr replay_thread; + + MessageHandlerRegistration message_handler_capture_thread_error { + Message::ID::CaptureThreadDone, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->handle_replay_thread_done(message.error); + } + }; +}; + +} /* namespace ui */ + +#endif/*__UI_REPLAY_VIEW_H__*/ diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index a66aed37..ff65b409 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -114,6 +114,7 @@ set(CPPSRC matched_filter.cpp spectrum_collector.cpp stream_input.cpp + stream_output.cpp dsp_squelch.cpp clock_recovery.cpp packet_builder.cpp @@ -368,12 +369,12 @@ set(MODE_CPPSRC ) DeclareTargets(PAFS afsk) -### Epar +### Replay -#set(MODE_CPPSRC -# proc_epar.cpp -#) -#DeclareTargets(PEPR epar) +set(MODE_CPPSRC + proc_replay.cpp +) +DeclareTargets(PREP replay) ### Tones diff --git a/firmware/baseband/proc_replay.cpp b/firmware/baseband/proc_replay.cpp new file mode 100644 index 00000000..da91cb76 --- /dev/null +++ b/firmware/baseband/proc_replay.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_replay.hpp" + +//#include "dsp_fir_taps.hpp" + +#include "event_m4.hpp" + +#include "utility.hpp" + +ReplayProcessor::ReplayProcessor() { + +} + +void ReplayProcessor::execute(const buffer_c8_t& buffer) { + /* 2.4576MHz, 2048 samples */ + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); + const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); + const auto& decimator_out = decim_1_out; + const auto& channel = decimator_out; + + if( stream ) { + const size_t bytes_to_write = sizeof(*decimator_out.p) * decimator_out.count; + const auto result = stream->write(decimator_out.p, bytes_to_write); + } + + feed_channel_stats(channel); + + /*spectrum_samples += channel.count; + if( spectrum_samples >= spectrum_interval_samples ) { + spectrum_samples -= spectrum_interval_samples; + channel_spectrum.feed(channel, channel_filter_pass_f, channel_filter_stop_f); + }*/ +} + +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::ReplayConfig: + replay_config(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void ReplayProcessor::replay_config(const ReplayConfigMessage& message) { + if( message.config ) { + stream = std::make_unique(message.config); + } else { + stream.reset(); + } +} + +int main() { + EventDispatcher event_dispatcher { std::make_unique() }; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_replay.hpp b/firmware/baseband/proc_replay.hpp new file mode 100644 index 00000000..34317845 --- /dev/null +++ b/firmware/baseband/proc_replay.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_REPLAY_HPP__ +#define __PROC_REPLAY_HPP__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" +//#include "rssi_thread.hpp" + +//#include "dsp_decimate.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: + // TODO: Repeated value needs to be transmitted from application side. + static constexpr size_t baseband_fs = 4000000; + //static constexpr auto spectrum_rate_hz = 50.0f; + + BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit }; + //RSSIThread rssi_thread { NORMALPRIO + 10 }; + + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + + std::unique_ptr stream; + + /*SpectrumCollector channel_spectrum; + size_t spectrum_interval_samples = 0; + size_t spectrum_samples = 0;*/ + + void replay_config(const ReplayConfigMessage& message); +}; + +#endif/*__PROC_REPLAY_HPP__*/ diff --git a/firmware/baseband/stream_output.cpp b/firmware/baseband/stream_output.cpp new file mode 100644 index 00000000..e70337e8 --- /dev/null +++ b/firmware/baseband/stream_output.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "stream_output.hpp" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +StreamOutput::StreamOutput(ReplayConfig* const config) : + fifo_buffers_empty { buffers_empty.data(), buffer_count_max_log2 }, + fifo_buffers_full { buffers_full.data(), buffer_count_max_log2 }, + config { config }, + data { std::make_unique(config->read_size * config->buffer_count) } +{ + config->fifo_buffers_empty = &fifo_buffers_empty; + config->fifo_buffers_full = &fifo_buffers_full; + + for(size_t i=0; ibuffer_count; i++) { + buffers[i] = { &(data.get()[i * config->read_size]), config->read_size }; + fifo_buffers_empty.in(&buffers[i]); + } +} + +size_t StreamOutput::write(const void* const data, const size_t length) { + const uint8_t* p = static_cast(data); + size_t written = 0; + + while( written < length ) { + if( !active_buffer ) { + // We need an empty buffer... + if( !fifo_buffers_empty.out(active_buffer) ) { + // ...but none are available. Samples were dropped. + break; + } + } + + const auto remaining = length - written; + written += active_buffer->write(&p[written], remaining); + + if( active_buffer->is_full() ) { + if( !fifo_buffers_full.in(active_buffer) ) { + // FIFO is fuil of buffers, there's no place for this one. + // Bail out of the loop, and try submitting the buffer in the + // next pass. + // This should never happen if the number of buffers is less + // than the capacity of the FIFO. + break; + } + active_buffer = nullptr; + creg::m4txevent::assert(); + } + } + + config->baseband_bytes_sent += length; + + return written; +} diff --git a/firmware/baseband/stream_output.hpp b/firmware/baseband/stream_output.hpp new file mode 100644 index 00000000..55be916a --- /dev/null +++ b/firmware/baseband/stream_output.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __STREAM_OUTPUT_H__ +#define __STREAM_OUTPUT_H__ + +#include "message.hpp" +#include "fifo.hpp" + +#include +#include +#include +#include + +class StreamOutput { +public: + StreamOutput(ReplayConfig* const config); + + size_t write(const void* const data, const size_t length); + +private: + static constexpr size_t buffer_count_max_log2 = 3; + static constexpr size_t buffer_count_max = 1U << buffer_count_max_log2; + + FIFO fifo_buffers_empty; + FIFO fifo_buffers_full; + std::array buffers; + std::array buffers_empty; + std::array buffers_full; + StreamBuffer* active_buffer { nullptr }; + ReplayConfig* const config { nullptr }; + std::unique_ptr data; +}; + +#endif/*__STREAM_OUTPUT_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 099439f1..c377ae52 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -66,23 +66,25 @@ public: DisplaySleep = 16, CaptureConfig = 17, CaptureThreadDone = 18, + ReplayConfig = 19, + ReplayThreadDone = 20, - TXDone = 20, - Retune = 21, - TonesConfigure = 22, - AFSKConfigure = 23, - PWMRSSIConfigure = 24, - OOKConfigure = 25, - RDSConfigure = 26, - AudioTXConfig = 27, - POCSAGConfigure = 28, - DTMFTXConfig = 29, - ADSBConfigure = 30, + TXDone = 30, + Retune = 31, + TonesConfigure = 32, + AFSKConfigure = 33, + PWMRSSIConfigure = 34, + OOKConfigure = 35, + RDSConfigure = 36, + AudioTXConfig = 37, + POCSAGConfigure = 38, + DTMFTXConfig = 39, + ADSBConfigure = 40, - POCSAGPacket = 31, + POCSAGPacket = 41, - FIFOSignal = 32, - FIFOData = 33, + FIFOSignal = 52, + FIFOData = 53, MAX }; @@ -492,6 +494,37 @@ public: CaptureConfig* const config; }; +struct ReplayConfig { + const size_t read_size; + const size_t buffer_count; + uint64_t baseband_bytes_sent; + FIFO* fifo_buffers_empty; + FIFO* fifo_buffers_full; + + constexpr ReplayConfig( + const size_t read_size, + const size_t buffer_count + ) : read_size { read_size }, + buffer_count { buffer_count }, + baseband_bytes_sent { 0 }, + fifo_buffers_empty { nullptr }, + fifo_buffers_full { nullptr } + { + } +}; + +class ReplayConfigMessage : public Message { +public: + constexpr ReplayConfigMessage( + ReplayConfig* const config + ) : Message { ID::ReplayConfig }, + config { config } + { + } + + ReplayConfig* const config; +}; + class TXDoneMessage : public Message { public: constexpr TXDoneMessage( @@ -715,4 +748,16 @@ public: uint32_t error; }; +class ReplayThreadDoneMessage : public Message { +public: + constexpr ReplayThreadDoneMessage( + uint32_t error = 0 + ) : Message { ID::ReplayThreadDone }, + error { error } + { + } + + uint32_t error; +}; + #endif/*__MESSAGE_H__*/ diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 309c4c65..71a32a6b 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -82,6 +82,7 @@ constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' }; constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' }; constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' }; constexpr image_tag_t image_tag_adsb_tx { 'P', 'A', 'D', 'S' }; +constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' }; constexpr image_tag_t image_tag_hackrf { 'H', 'R', 'F', '1' }; diff --git a/firmware/graphics/PP_REPLAY.png b/firmware/graphics/PP_REPLAY.png new file mode 100644 index 00000000..445884da Binary files /dev/null and b/firmware/graphics/PP_REPLAY.png differ