mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Add APRS Receiving App
This commit is contained in:
parent
de92faf67b
commit
f15cf78101
@ -140,6 +140,7 @@ set(CPPSRC
|
||||
${COMMON}/png_writer.cpp
|
||||
${COMMON}/pocsag.cpp
|
||||
${COMMON}/pocsag_packet.cpp
|
||||
${COMMON}/aprs_packet.cpp
|
||||
${COMMON}/portapack_io.cpp
|
||||
${COMMON}/portapack_persistent_memory.cpp
|
||||
${COMMON}/portapack_shared_memory.cpp
|
||||
@ -218,6 +219,7 @@ set(CPPSRC
|
||||
apps/ui_adsb_rx.cpp
|
||||
apps/ui_adsb_tx.cpp
|
||||
apps/ui_afsk_rx.cpp
|
||||
apps/ui_aprs_rx.cpp
|
||||
apps/ui_btle_rx.cpp
|
||||
apps/ui_nrf_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
|
@ -31,6 +31,7 @@ namespace ui
|
||||
console.writeln("N0vaPixel,klockee,GullCode");
|
||||
console.writeln("jamesshao8,ITAxReal,rascafr");
|
||||
console.writeln("mcules,dqs105,strijar");
|
||||
console.writeln("East2West");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
|
409
firmware/application/apps/ui_aprs_rx.cpp
Normal file
409
firmware/application/apps/ui_aprs_rx.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_aprs_rx.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
void APRSLogger::log_raw_data(const std::string& data) {
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
|
||||
log_file.write_entry(datetime, data);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
template<>
|
||||
void RecentEntriesTable<APRSRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style
|
||||
) {
|
||||
char aged_color;
|
||||
Color target_color;
|
||||
auto entry_age = entry.age;
|
||||
|
||||
target_color = Color::green();
|
||||
|
||||
aged_color = 0x10;
|
||||
std::string entry_string = "\x1B";
|
||||
entry_string += aged_color;
|
||||
|
||||
entry_string += entry.source_formatted;
|
||||
entry_string.append(10 - entry.source_formatted.size(),' ');
|
||||
entry_string += " ";
|
||||
entry_string += (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+");
|
||||
entry_string += " ";
|
||||
entry_string += entry.time_string;
|
||||
|
||||
painter.draw_string(
|
||||
target_rect.location(),
|
||||
style,
|
||||
entry_string
|
||||
);
|
||||
|
||||
if (entry.has_position){
|
||||
painter.draw_bitmap(target_rect.location() + Point(12 * 8, 0), bitmap_target, target_color, style.background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void APRSRxView::focus() {
|
||||
options_region.focus();
|
||||
}
|
||||
|
||||
void APRSRxView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_tuning_frequency(f);
|
||||
}
|
||||
|
||||
APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect) : View(parent_rect) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_aprs_rx);
|
||||
|
||||
add_children({
|
||||
&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&options_region,
|
||||
&field_frequency,
|
||||
&record_view,
|
||||
&console
|
||||
});
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
record_view.set_sampling_rate(24000);
|
||||
|
||||
options_region.on_change = [this](size_t, int32_t i) {
|
||||
if (i == 0){
|
||||
field_frequency.set_value(144390000);
|
||||
}
|
||||
if(i == 1){
|
||||
field_frequency.set_value(144800000);
|
||||
}
|
||||
if(i == 2){
|
||||
field_frequency.set_value(145175000);
|
||||
}
|
||||
};
|
||||
|
||||
field_frequency.set_value(receiver_model.tuning_frequency());
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
options_region.set_selected_index(0, true);
|
||||
|
||||
logger = std::make_unique<APRSLogger>();
|
||||
if (logger)
|
||||
logger->append("APRS_RX_LOG.TXT");
|
||||
|
||||
baseband::set_aprs(1200);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
void APRSRxView::on_packet(const APRSPacketMessage* message){
|
||||
std::string str_console = "\x1B";
|
||||
|
||||
aprs::APRSPacket packet = message->packet;
|
||||
|
||||
std::string stream_text = packet.get_stream_text();
|
||||
str_console += (char)((console_color++ & 3) + 9);
|
||||
str_console += stream_text;
|
||||
|
||||
if(logger){
|
||||
logger->log_raw_data(stream_text);
|
||||
}
|
||||
|
||||
//if(reset_console){ //having more than one console causes issues when switching tabs where one is disabled, and the other enabled breaking the scoll setup.
|
||||
// console.on_hide();
|
||||
// console.on_show();
|
||||
// reset_console = false;
|
||||
//}
|
||||
|
||||
console.writeln(str_console);
|
||||
}
|
||||
|
||||
void APRSRxView::on_data(uint32_t value, bool is_data) {
|
||||
std::string str_console = "\x1B";
|
||||
std::string str_byte = "";
|
||||
|
||||
if (is_data) {
|
||||
// Colorize differently after message splits
|
||||
str_console += (char)((console_color & 3) + 9);
|
||||
|
||||
if (value == '\n') {
|
||||
// Message split
|
||||
console.writeln("");
|
||||
console_color++;
|
||||
|
||||
if (logger) {
|
||||
logger->log_raw_data(str_log);
|
||||
str_log = "";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((value >= 32) && (value < 127)) {
|
||||
str_console += (char)value; // Printable
|
||||
str_byte += (char)value;
|
||||
} else {
|
||||
str_console += "[" + to_string_hex(value, 2) + "]"; // Not printable
|
||||
str_byte += "[" + to_string_hex(value, 2) + "]";
|
||||
}
|
||||
|
||||
console.write(str_console);
|
||||
|
||||
if (logger) str_log += str_byte;
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void APRSRxView::on_show(){
|
||||
//some bug where the display scroll area is set to the entire screen when switching from the list tab with details showing back to the stream view.
|
||||
//reset_console = true;
|
||||
}
|
||||
|
||||
APRSRxView::~APRSRxView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void APRSTableView::on_show_list() {
|
||||
details_view.hidden(true);
|
||||
recent_entries_view.hidden(false);
|
||||
send_updates = false;
|
||||
focus();
|
||||
}
|
||||
|
||||
void APRSTableView::on_show_detail(const APRSRecentEntry& entry) {
|
||||
recent_entries_view.hidden(true);
|
||||
details_view.hidden(false);
|
||||
details_view.set_entry(entry);
|
||||
details_view.update();
|
||||
details_view.focus();
|
||||
detailed_entry_key = entry.key();
|
||||
send_updates = true;
|
||||
}
|
||||
|
||||
APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec) : View(parent_rec), nav_ {nav} {
|
||||
add_children({
|
||||
&recent_entries_view,
|
||||
&details_view
|
||||
});
|
||||
|
||||
hidden(true);
|
||||
|
||||
details_view.hidden(true);
|
||||
|
||||
recent_entries_view.set_parent_rect({0, 0, 240, 280});
|
||||
details_view.set_parent_rect({0, 0, 240, 280});
|
||||
|
||||
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
|
||||
this->on_show_detail(entry);
|
||||
};
|
||||
|
||||
details_view.on_close = [this]() {
|
||||
this->on_show_list();
|
||||
};
|
||||
|
||||
|
||||
/* for(size_t i = 0; i <32 ; i++){
|
||||
std::string id = "test" + i;
|
||||
auto& entry = ::on_packet(recent, i);
|
||||
entry.set_source_formatted(id);
|
||||
}
|
||||
|
||||
recent_entries_view.set_dirty(); */
|
||||
|
||||
/*
|
||||
|
||||
std::string str1 = "test1";
|
||||
std::string str12 = "test2";
|
||||
std::string str13 = "test2";
|
||||
|
||||
auto& entry = ::on_packet(recent, 0x1);
|
||||
entry.set_source_formatted(str1);
|
||||
|
||||
auto& entry2 = ::on_packet(recent, 0x2);
|
||||
entry2.set_source_formatted(str12);
|
||||
|
||||
auto& entry3 = ::on_packet(recent, 0x2);
|
||||
entry2.set_source_formatted(str13);
|
||||
|
||||
recent_entries_view.set_dirty();
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
void APRSTableView::on_pkt(const APRSPacketMessage* message){
|
||||
aprs::APRSPacket packet = message->packet;
|
||||
rtc::RTC datetime;
|
||||
std::string str_timestamp;
|
||||
std::string source_formatted = packet.get_source_formatted();
|
||||
std::string info_string = packet.get_stream_text();
|
||||
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
auto& entry = ::on_packet(recent, packet.get_source());
|
||||
entry.reset_age();
|
||||
entry.inc_hit();
|
||||
str_timestamp = to_string_datetime(datetime, HMS);
|
||||
entry.set_time_string(str_timestamp);
|
||||
entry.set_info_string(info_string);
|
||||
|
||||
entry.set_source_formatted(source_formatted);
|
||||
|
||||
if(entry.has_position && !packet.has_position()){
|
||||
//maintain position info
|
||||
}
|
||||
else {
|
||||
entry.set_has_position(packet.has_position());
|
||||
entry.set_pos(packet.get_position());
|
||||
}
|
||||
|
||||
if( entry.key() == details_view.entry().key() ) {
|
||||
details_view.set_entry(entry);
|
||||
details_view.update();
|
||||
}
|
||||
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
void APRSTableView::on_show(){
|
||||
on_show_list();
|
||||
}
|
||||
|
||||
void APRSTableView::on_hide(){
|
||||
details_view.hidden(true);
|
||||
}
|
||||
|
||||
void APRSTableView::focus(){
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
APRSTableView::~APRSTableView(){
|
||||
|
||||
}
|
||||
|
||||
void APRSDetailsView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
void APRSDetailsView::set_entry(const APRSRecentEntry& entry){
|
||||
entry_copy = entry;
|
||||
}
|
||||
|
||||
void APRSDetailsView::update() {
|
||||
if(!hidden()){
|
||||
uint32_t age = entry_copy.age;
|
||||
|
||||
console.clear(true);
|
||||
console.write(entry_copy.info_string);
|
||||
|
||||
button_see_map.hidden(!entry_copy.has_position);
|
||||
}
|
||||
|
||||
if (send_updates)
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0);
|
||||
}
|
||||
|
||||
APRSDetailsView::~APRSDetailsView() {
|
||||
}
|
||||
|
||||
APRSDetailsView::APRSDetailsView(
|
||||
NavigationView& nav
|
||||
)
|
||||
{
|
||||
add_children({
|
||||
&console,
|
||||
&button_done,
|
||||
&button_see_map
|
||||
});
|
||||
|
||||
button_done.on_select = [this, &nav](Button&) {
|
||||
console.clear(true);
|
||||
this->on_close();
|
||||
};
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
geomap_view = nav.push<GeoMapView>(
|
||||
entry_copy.source_formatted,
|
||||
0, //entry_copy.pos.altitude,
|
||||
GeoPos::alt_unit::FEET,
|
||||
entry_copy.pos.latitude,
|
||||
entry_copy.pos.longitude,
|
||||
0, /*entry_copy.velo.heading,*/
|
||||
[this]() {
|
||||
send_updates = false;
|
||||
hidden(false);
|
||||
update();
|
||||
});
|
||||
send_updates = true;
|
||||
hidden(true);
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
APRSRXView::APRSRXView(NavigationView& nav) : nav_ {nav} {
|
||||
add_children({
|
||||
&tab_view,
|
||||
&view_stream,
|
||||
&view_table
|
||||
});
|
||||
}
|
||||
|
||||
void APRSRXView::focus(){
|
||||
tab_view.focus();
|
||||
}
|
||||
|
||||
APRSRXView::~APRSRXView() {
|
||||
|
||||
}
|
||||
} /* namespace ui */
|
276
firmware/application/apps/ui_aprs_rx.hpp
Normal file
276
firmware/application/apps/ui_aprs_rx.hpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_APRS_RX_H__
|
||||
#define __UI_APRS_RX_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_record_view.hpp" // DEBUG
|
||||
#include "ui_geomap.hpp"
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
class APRSLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void log_raw_data(const std::string& data);
|
||||
|
||||
private:
|
||||
LogFile log_file { };
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
struct APRSRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffffffffffff;
|
||||
|
||||
uint16_t hits { 0 };
|
||||
uint32_t age { 0 };
|
||||
|
||||
uint64_t source;
|
||||
std::string source_formatted { " " };
|
||||
std::string time_string { "" };
|
||||
std::string info_string { "" };
|
||||
|
||||
aprs::aprs_pos pos;
|
||||
bool has_position = false;
|
||||
APRSRecentEntry(uint64_t src)
|
||||
{
|
||||
source = src;
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return source;
|
||||
}
|
||||
|
||||
void set_source_formatted(std::string& new_source) {
|
||||
source_formatted = new_source;
|
||||
}
|
||||
|
||||
void inc_hit() {
|
||||
hits++;
|
||||
}
|
||||
|
||||
void set_info_string(std::string& new_info_string) {
|
||||
info_string = new_info_string;
|
||||
}
|
||||
|
||||
void set_time_string(std::string& new_time_string) {
|
||||
time_string = new_time_string;
|
||||
}
|
||||
|
||||
void set_pos(aprs::aprs_pos pos_in){
|
||||
pos = pos_in;
|
||||
}
|
||||
|
||||
void set_has_position(bool has_pos){
|
||||
has_position = has_pos;
|
||||
}
|
||||
|
||||
void reset_age() {
|
||||
age = 0;
|
||||
}
|
||||
|
||||
void inc_age() {
|
||||
age++;
|
||||
}
|
||||
};
|
||||
|
||||
class APRSDetailsView : public View {
|
||||
public:
|
||||
APRSDetailsView(NavigationView&);
|
||||
~APRSDetailsView();
|
||||
|
||||
APRSDetailsView(const APRSDetailsView&) = delete;
|
||||
APRSDetailsView(APRSDetailsView&&) = delete;
|
||||
APRSDetailsView& operator=(const APRSDetailsView&) = delete;
|
||||
APRSDetailsView& operator=(APRSDetailsView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
void update();
|
||||
void set_entry(const APRSRecentEntry& entry);
|
||||
|
||||
const APRSRecentEntry& entry() const { return entry_copy; };
|
||||
|
||||
std::string title() const override { return "Details"; };
|
||||
std::function<void(void)> on_close { };
|
||||
|
||||
private:
|
||||
APRSRecentEntry entry_copy { 0 };
|
||||
GeoMapView* geomap_view { nullptr };
|
||||
bool send_updates { false };
|
||||
|
||||
Console console {
|
||||
{ 0, 0 * 16, 240, 224 }
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 160, 14 * 16, 8 * 8, 3 * 16 },
|
||||
"Close"
|
||||
};
|
||||
|
||||
Button button_see_map {
|
||||
{ 80, 14 * 16, 8 * 8, 3 * 16 },
|
||||
"Map"
|
||||
};
|
||||
};
|
||||
|
||||
using APRSRecentEntries = RecentEntries<APRSRecentEntry>;
|
||||
|
||||
class APRSTableView: public View {
|
||||
public:
|
||||
APRSTableView(NavigationView& nav, Rect parent_rec);
|
||||
~APRSTableView();
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
void on_pkt(const APRSPacketMessage* message);
|
||||
|
||||
std::string title() const override { return "Stations"; };
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
const RecentEntriesColumns columns { {
|
||||
{ "Source", 9 },
|
||||
{ "Loc", 6 },
|
||||
{ "Hits", 4 },
|
||||
{ "Time", 8 }
|
||||
} };
|
||||
APRSRecentEntries recent { };
|
||||
RecentEntriesView<RecentEntries<APRSRecentEntry>> recent_entries_view { columns, recent };
|
||||
APRSDetailsView details_view { nav_ };
|
||||
uint32_t detailed_entry_key { 0 };
|
||||
bool send_updates { false };
|
||||
|
||||
void on_show_list();
|
||||
void on_show_detail(const APRSRecentEntry& entry);
|
||||
};
|
||||
|
||||
class APRSRxView : public View {
|
||||
public:
|
||||
APRSRxView(NavigationView& nav, Rect parent_rect);
|
||||
~APRSRxView();
|
||||
|
||||
void on_show() override;
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "APRS RX"; };
|
||||
void on_packet(const APRSPacketMessage* message);
|
||||
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
bool reset_console = false;
|
||||
|
||||
uint8_t console_color { 0 };
|
||||
std::string str_log { "" };
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
};
|
||||
LNAGainField field_lna {
|
||||
{ 15 * 8, 0 * 16 }
|
||||
};
|
||||
VGAGainField field_vga {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
RSSI rssi {
|
||||
{ 21 * 8, 0, 6 * 8, 4 },
|
||||
};
|
||||
Channel channel {
|
||||
{ 21 * 8, 5, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
OptionsField options_region {
|
||||
{ 0 * 8, 0 * 8 },
|
||||
3,
|
||||
{
|
||||
{ "NA ", 0 },
|
||||
{ "EUR", 1 },
|
||||
{ "AUS", 2 }
|
||||
}
|
||||
};
|
||||
|
||||
FrequencyField field_frequency {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
||||
u"AFS_????", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
Console console {
|
||||
{ 0, 2 * 16, 240, 240 }
|
||||
};
|
||||
|
||||
void update_freq(rf::Frequency f);
|
||||
|
||||
std::unique_ptr<APRSLogger> logger { };
|
||||
};
|
||||
|
||||
class APRSRXView : public View {
|
||||
public:
|
||||
APRSRXView(NavigationView& nav);
|
||||
~APRSRXView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "APRS RX"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Rect view_rect = { 0, 3 * 8, 240, 280 };
|
||||
|
||||
APRSRxView view_stream { nav_, view_rect };
|
||||
APRSTableView view_table { nav_, view_rect };
|
||||
|
||||
TabView tab_view {
|
||||
{ "Stream", Color::cyan(), &view_stream },
|
||||
{ "List", Color::yellow(), &view_table }
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet {
|
||||
Message::ID::APRSPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const APRSPacketMessage*>(p);
|
||||
this->view_stream.on_packet(message);
|
||||
this->view_table.on_pkt(message);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_APRS_RX_H__*/
|
@ -130,6 +130,13 @@ void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_aprs(const uint32_t baudrate) {
|
||||
const APRSRxConfigureMessage message {
|
||||
baudrate
|
||||
};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
|
||||
const BTLERxConfigureMessage message {
|
||||
baudrate,
|
||||
|
@ -68,6 +68,7 @@ void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phas
|
||||
const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
void kill_afsk();
|
||||
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
void set_aprs(const uint32_t baudrate);
|
||||
|
||||
void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
|
||||
|
@ -179,6 +179,24 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( event == ui::KeyEvent::Up ) {
|
||||
if(selected_key == recent.front().key()){
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
advance(-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if( event == ui::KeyEvent::Down ) {
|
||||
if( selected_key == recent.back().key()) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
advance(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -264,7 +282,7 @@ public:
|
||||
// TODO: What happens here shouldn't matter if I do proper damage detection!
|
||||
}
|
||||
|
||||
void on_focus() override {
|
||||
void focus() override {
|
||||
_table.focus();
|
||||
}
|
||||
|
||||
|
@ -264,8 +264,13 @@ void GeoMapView::focus() {
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle) {
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
|
||||
// Stupid hack to avoid an event loop
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
geopos.set_report_change(true);
|
||||
|
||||
geomap.set_angle(angle);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_dirty();
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "ui_adsb_rx.hpp"
|
||||
#include "ui_adsb_tx.hpp"
|
||||
#include "ui_afsk_rx.hpp"
|
||||
#include "ui_aprs_rx.hpp"
|
||||
#include "ui_btle_rx.hpp"
|
||||
#include "ui_nrf_rx.hpp"
|
||||
#include "ui_aprs_tx.hpp"
|
||||
@ -468,7 +469,8 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
{ "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>(); } },
|
||||
/*{ "APRS", ui::Color::dark_grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "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
|
||||
{ "LoRa", ui::Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
|
@ -312,6 +312,14 @@ set(MODE_CPPSRC
|
||||
proc_afskrx.cpp
|
||||
)
|
||||
DeclareTargets(PAFR afskrx)
|
||||
|
||||
### APRS RX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_aprsrx.cpp
|
||||
)
|
||||
DeclareTargets(PAPR aprsrx)
|
||||
|
||||
### NRF RX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
|
258
firmware/baseband/proc_aprsrx.cpp
Normal file
258
firmware/baseband/proc_aprsrx.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "proc_aprsrx.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "stdio.h"
|
||||
|
||||
void APRSRxProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// This is called at 3072000 / 2048 = 1500Hz
|
||||
|
||||
if (!configured) return;
|
||||
|
||||
// FM demodulation
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer); // 2048 / 8 = 256 (512 I/Q samples)
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); // 256 / 8 = 32 (64 I/Q samples)
|
||||
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); // 32 / 2 = 16 (32 I/Q samples)
|
||||
|
||||
feed_channel_stats(channel_out);
|
||||
|
||||
auto audio = demod.execute(channel_out, audio_buffer);
|
||||
|
||||
audio_output.write(audio);
|
||||
|
||||
// Audio signal processing
|
||||
for (size_t c = 0; c < audio.count; c++) {
|
||||
|
||||
const int32_t sample_int = audio.p[c] * 32768.0f;
|
||||
int32_t current_sample = __SSAT(sample_int, 16);
|
||||
|
||||
current_sample /= 128;
|
||||
|
||||
// Delay line put
|
||||
delay_line[delay_line_index & 0x3F] = current_sample;
|
||||
|
||||
// Delay line get, and LPF
|
||||
sample_mixed = (delay_line[(delay_line_index - (samples_per_bit/2)) & 0x3F] * current_sample) / 4;
|
||||
sample_filtered = prev_mixed + sample_mixed + (prev_filtered / 2);
|
||||
|
||||
delay_line_index++;
|
||||
|
||||
prev_filtered = sample_filtered;
|
||||
prev_mixed = sample_mixed;
|
||||
|
||||
// Slice
|
||||
sample_bits <<= 1;
|
||||
|
||||
uint8_t bit = (sample_filtered < -20) ? 1 : 0;
|
||||
sample_bits |= bit;
|
||||
|
||||
/*
|
||||
int16_t scaled = bit == 1 ? 32767 : -32767;
|
||||
|
||||
if( stream ) {
|
||||
const size_t bytes_to_write = sizeof(scaled) * 1;
|
||||
const auto result = stream->write(&scaled, bytes_to_write);
|
||||
}
|
||||
*/
|
||||
|
||||
// Check for "clean" transition: either 0011 or 1100
|
||||
if ((((sample_bits >> 2) ^ sample_bits) & 3) == 3) {
|
||||
// Adjust phase
|
||||
if (phase < 0x8000)
|
||||
phase += 0x800; // Is this a proper value ?
|
||||
else
|
||||
phase -= 0x800;
|
||||
}
|
||||
|
||||
phase += phase_inc;
|
||||
|
||||
if (phase >= 0x10000) {
|
||||
phase &= 0xFFFF;
|
||||
|
||||
if (true) {
|
||||
uint8_t bit;
|
||||
if(__builtin_popcount(sample_bits & 0xFF) >= 0x05){
|
||||
bit = 0x1;
|
||||
}
|
||||
else {
|
||||
bit = 0x0;
|
||||
}
|
||||
|
||||
if(parse_bit(bit)){
|
||||
parse_packet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APRSRxProcessor::parse_packet(){
|
||||
//validate crc
|
||||
if(packet_buffer_size >= aprs::APRS_MIN_LENGTH){
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
for(size_t i = 0; i < packet_buffer_size; i++){
|
||||
uint8_t byte = packet_buffer[i];
|
||||
crc = ((crc >> 8) ^ crc_ccitt_tab[(crc ^ byte) & 0xFF]) & 0xFFFF;
|
||||
}
|
||||
|
||||
if(crc == 0xF0B8){
|
||||
parse_ax25();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APRSRxProcessor::parse_ax25(){
|
||||
aprs_packet.clear();
|
||||
aprs_packet.set_valid_checksum(true);
|
||||
|
||||
for(size_t i = 0; i < packet_buffer_size; i++){
|
||||
aprs_packet.set(i, packet_buffer[i]);
|
||||
}
|
||||
|
||||
APRSPacketMessage packet_message { aprs_packet };
|
||||
shared_memory.application_queue.push(packet_message);
|
||||
}
|
||||
|
||||
bool APRSRxProcessor::parse_bit(const uint8_t current_bit){
|
||||
uint8_t decoded_bit = ~(current_bit ^ last_bit) & 0x1;
|
||||
last_bit = current_bit;
|
||||
|
||||
int16_t log = decoded_bit == 0 ? -32768 : 32767;
|
||||
|
||||
//if( stream ) {
|
||||
// const size_t bytes_to_write = sizeof(log) * 1;
|
||||
// const auto result = stream->write(&log, bytes_to_write);
|
||||
// }
|
||||
|
||||
if(decoded_bit & 0x1){
|
||||
if(ones_count < 8){
|
||||
ones_count++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(ones_count > 6){ //not valid
|
||||
state = WAIT_FLAG;
|
||||
current_byte = 0;
|
||||
ones_count = 0;
|
||||
byte_index = 0;
|
||||
packet_buffer_size = 0;
|
||||
return false;
|
||||
}
|
||||
else if(ones_count == 6){ //flag
|
||||
bool done = false;
|
||||
if(state == IN_FRAME){
|
||||
done = true;
|
||||
}
|
||||
else {
|
||||
packet_buffer_size = 0;
|
||||
}
|
||||
state = WAIT_FRAME;
|
||||
current_byte = 0;
|
||||
ones_count = 0;
|
||||
byte_index = 0;
|
||||
|
||||
return done;
|
||||
}
|
||||
else if(ones_count == 5){ //bit stuff
|
||||
ones_count = 0;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
ones_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//store
|
||||
current_byte = current_byte >> 1;
|
||||
current_byte |= (decoded_bit == 0x1 ? 0x80 : 0x0);
|
||||
byte_index++;
|
||||
|
||||
if(byte_index >= 8){
|
||||
byte_index = 0;
|
||||
if(state == WAIT_FRAME){
|
||||
state = IN_FRAME;
|
||||
}
|
||||
|
||||
if(state == IN_FRAME){
|
||||
if(packet_buffer_size + 1 >= 256){
|
||||
state = WAIT_FLAG;
|
||||
current_byte = 0;
|
||||
ones_count = 0;
|
||||
byte_index = 0;
|
||||
packet_buffer_size = 0;
|
||||
return false;
|
||||
}
|
||||
packet_buffer[packet_buffer_size++] = current_byte;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void APRSRxProcessor::on_message(const Message* const message) {
|
||||
if (message->id == Message::ID::APRSRxConfigure)
|
||||
configure(*reinterpret_cast<const APRSRxConfigureMessage*>(message));
|
||||
if(message->id == Message::ID::CaptureConfig)
|
||||
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
||||
}
|
||||
|
||||
void APRSRxProcessor::capture_config(const CaptureConfigMessage& message) {
|
||||
if( message.config ) {
|
||||
//stream = std::make_unique<StreamInput>(message.config);
|
||||
audio_output.set_stream(std::make_unique<StreamInput>(message.config));
|
||||
} else {
|
||||
//stream.reset();
|
||||
audio_output.set_stream(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void APRSRxProcessor::configure(const APRSRxConfigureMessage& message) {
|
||||
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_11k0_decim_1.taps, 131072);
|
||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||
demod.configure(audio_fs, 5000);
|
||||
|
||||
audio_output.configure(audio_24k_hpf_300hz_config, audio_24k_deemph_300_6_config, 0);
|
||||
|
||||
samples_per_bit = audio_fs / message.baudrate;
|
||||
|
||||
phase_inc = (0x10000 * message.baudrate) / audio_fs;
|
||||
phase = 0;
|
||||
|
||||
// Delay line
|
||||
delay_line_index = 0;
|
||||
|
||||
state = WAIT_FLAG;
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher { std::make_unique<APRSRxProcessor>() };
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
148
firmware/baseband/proc_aprsrx.hpp
Normal file
148
firmware/baseband/proc_aprsrx.hpp
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PROC_APRSRX_H__
|
||||
#define __PROC_APRSRX_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "stream_input.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
|
||||
#include "fifo.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#include "aprs_packet.hpp"
|
||||
|
||||
static uint16_t crc_ccitt_tab[256] = {
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||
};
|
||||
|
||||
class APRSRxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
static constexpr size_t audio_fs = baseband_fs / 8 / 8 / 2;
|
||||
|
||||
size_t samples_per_bit { };
|
||||
|
||||
enum State {
|
||||
WAIT_FLAG,
|
||||
WAIT_FRAME,
|
||||
IN_FRAME
|
||||
};
|
||||
|
||||
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
|
||||
RSSIThread rssi_thread { NORMALPRIO + 10 };
|
||||
|
||||
std::array<complex16_t, 512> dst { };
|
||||
const buffer_c16_t dst_buffer {
|
||||
dst.data(),
|
||||
dst.size()
|
||||
};
|
||||
std::array<float, 32> audio { };
|
||||
const buffer_f32_t audio_buffer {
|
||||
audio.data(),
|
||||
audio.size()
|
||||
};
|
||||
|
||||
// Array size ok down to 375 bauds (24000 / 375)
|
||||
std::array<int32_t, 64> delay_line { 0 };
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0 { };
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1 { };
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter { };
|
||||
|
||||
std::unique_ptr<StreamInput> stream { };
|
||||
|
||||
dsp::demodulate::FM demod { };
|
||||
|
||||
AudioOutput audio_output { };
|
||||
|
||||
State state { };
|
||||
size_t delay_line_index { };
|
||||
uint32_t bit_counter { 0 };
|
||||
uint32_t word_bits { 0 };
|
||||
uint32_t sample_bits { 0 };
|
||||
uint32_t phase { }, phase_inc { };
|
||||
int32_t sample_mixed { }, prev_mixed { }, sample_filtered { }, prev_filtered { };
|
||||
uint8_t last_bit;
|
||||
uint8_t ones_count = 0;
|
||||
uint8_t current_byte = 0;
|
||||
uint8_t byte_index = 0;
|
||||
uint8_t packet_buffer[256];
|
||||
size_t packet_buffer_size = 0;
|
||||
|
||||
bool configured { false };
|
||||
bool wait_start { };
|
||||
bool bit_value { };
|
||||
|
||||
aprs::APRSPacket aprs_packet;
|
||||
|
||||
void configure(const APRSRxConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
void parse_packet();
|
||||
bool parse_bit(const uint8_t bit);
|
||||
void parse_ax25();
|
||||
};
|
||||
|
||||
#endif/*__PROC_TPMS_H__*/
|
27
firmware/common/aprs_packet.cpp
Normal file
27
firmware/common/aprs_packet.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "aprs_packet.hpp"
|
||||
|
||||
namespace aprs {
|
||||
|
||||
} /* namespace zwave */
|
495
firmware/common/aprs_packet.hpp
Normal file
495
firmware/common/aprs_packet.hpp
Normal file
@ -0,0 +1,495 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __APRS_PACKET_H__
|
||||
#define __APRS_PACKET_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cctype>
|
||||
|
||||
#include "baseband.hpp"
|
||||
|
||||
namespace aprs {
|
||||
|
||||
const int APRS_MIN_LENGTH = 18; //14 bytes address, control byte and pid. 2 CRC.
|
||||
|
||||
struct aprs_pos{
|
||||
float latitude;
|
||||
float longitude;
|
||||
uint8_t symbol_code;
|
||||
uint8_t sym_table_id;
|
||||
};
|
||||
|
||||
enum ADDRESS_TYPE {
|
||||
SOURCE,
|
||||
DESTINATION,
|
||||
REPEATER
|
||||
};
|
||||
|
||||
class APRSPacket {
|
||||
public:
|
||||
void set_timestamp(const Timestamp& value) {
|
||||
timestamp_ = value;
|
||||
}
|
||||
|
||||
Timestamp timestamp() const {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
void set(const size_t index, const uint8_t data) {
|
||||
payload[index] = data;
|
||||
|
||||
if(index + 1 > payload_size){
|
||||
payload_size = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t operator[](const size_t index) const {
|
||||
return payload[index];
|
||||
}
|
||||
|
||||
|
||||
uint8_t size() const {
|
||||
return payload_size;
|
||||
}
|
||||
|
||||
void set_valid_checksum(const bool valid) {
|
||||
valid_checksum = valid;
|
||||
}
|
||||
|
||||
bool is_valid_checksum() const {
|
||||
return valid_checksum;
|
||||
}
|
||||
|
||||
uint64_t get_source(){
|
||||
uint64_t source = 0x0;
|
||||
|
||||
for(uint8_t i = SOURCE_START; i < SOURCE_START + ADDRESS_SIZE; i++){
|
||||
source |= ( ((uint64_t)payload[i]) << ((i - SOURCE_START) * 8));
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string get_source_formatted(){
|
||||
parse_address(SOURCE_START, SOURCE);
|
||||
return std::string(address_buffer);
|
||||
}
|
||||
|
||||
std::string get_destination_formatted(){
|
||||
parse_address(DESTINATION_START, DESTINATION);
|
||||
return std::string(address_buffer);
|
||||
}
|
||||
|
||||
std::string get_digipeaters_formatted(){
|
||||
uint8_t position = DIGIPEATER_START;
|
||||
bool has_more = parse_address(SOURCE_START, REPEATER);
|
||||
|
||||
std::string repeaters = "";
|
||||
while(has_more){
|
||||
has_more = parse_address(position, REPEATER);
|
||||
repeaters += std::string(address_buffer);
|
||||
|
||||
position += ADDRESS_SIZE;
|
||||
|
||||
if(has_more){
|
||||
repeaters += ">";
|
||||
}
|
||||
}
|
||||
|
||||
return repeaters;
|
||||
}
|
||||
|
||||
uint8_t get_number_of_digipeaters(){
|
||||
uint8_t position = DIGIPEATER_START;
|
||||
bool has_more = parse_address(SOURCE_START, REPEATER);
|
||||
uint8_t repeaters = 0;
|
||||
while(has_more){
|
||||
has_more = parse_address(position, REPEATER);
|
||||
position += ADDRESS_SIZE;
|
||||
repeaters++;
|
||||
}
|
||||
|
||||
return repeaters;
|
||||
}
|
||||
|
||||
uint8_t get_information_start_index(){
|
||||
return DIGIPEATER_START + (get_number_of_digipeaters() * ADDRESS_SIZE) + 2;
|
||||
}
|
||||
|
||||
std::string get_information_text_formatted(){
|
||||
std::string information_text = "";
|
||||
for(uint8_t i = get_information_start_index(); i < payload_size - 2; i++){
|
||||
information_text += payload[i];
|
||||
}
|
||||
|
||||
return information_text;
|
||||
}
|
||||
|
||||
std::string get_stream_text(){
|
||||
std::string stream = get_source_formatted() + ">" + get_destination_formatted() + ";" + get_digipeaters_formatted() + ";" + get_information_text_formatted();
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
char get_data_type_identifier(){
|
||||
char ident = '\0';
|
||||
for(uint8_t i = get_information_start_index(); i < payload_size - 2; i++){
|
||||
ident = payload[i];
|
||||
break;
|
||||
}
|
||||
return ident;
|
||||
}
|
||||
|
||||
bool has_position(){
|
||||
char ident = get_data_type_identifier();
|
||||
|
||||
return
|
||||
ident == '!' ||
|
||||
ident == '=' ||
|
||||
ident == '/' ||
|
||||
ident == '@' ||
|
||||
ident == ';' ||
|
||||
ident == '`' ||
|
||||
ident == '\''||
|
||||
ident == 0x1d||
|
||||
ident == 0x1c;
|
||||
}
|
||||
|
||||
aprs_pos get_position(){
|
||||
aprs::aprs_pos pos;
|
||||
|
||||
char ident = get_data_type_identifier();
|
||||
std::string info_text = get_information_text_formatted();
|
||||
|
||||
std::string lat_str, lng_str;
|
||||
char first;
|
||||
//bool supports_compression = true;
|
||||
bool is_mic_e_format = false;
|
||||
std::string::size_type start;
|
||||
|
||||
switch(ident){
|
||||
case '/':
|
||||
case '@':
|
||||
start = 8;
|
||||
break;
|
||||
case '=':
|
||||
case '!':
|
||||
start = 1;
|
||||
break;
|
||||
case ';':
|
||||
start = 18;
|
||||
//supports_compression = false;
|
||||
break;
|
||||
case '`':
|
||||
case '\'':
|
||||
case 0x1c:
|
||||
case 0x1d:
|
||||
is_mic_e_format = true;
|
||||
break;
|
||||
default:
|
||||
return pos;
|
||||
}
|
||||
if(is_mic_e_format){
|
||||
parse_mic_e_format(pos);
|
||||
}
|
||||
else {
|
||||
if(start < info_text.size()){
|
||||
first = info_text.at(start);
|
||||
|
||||
if(std::isdigit(first)){
|
||||
if(start + 18 < info_text.size()){
|
||||
lat_str = info_text.substr(start, 8);
|
||||
pos.sym_table_id = info_text.at(start + 8);
|
||||
lng_str = info_text.substr(start + 9, 9);
|
||||
pos.symbol_code = info_text.at(start + 18);
|
||||
|
||||
pos.latitude = parse_lat_str(lat_str);
|
||||
pos.longitude = parse_lng_str(lng_str);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
if(start + 9 < info_text.size()){
|
||||
pos.sym_table_id = info_text.at(start);
|
||||
lat_str = info_text.substr(start + 1, 4);
|
||||
lng_str = info_text.substr(start + 5, 4);
|
||||
pos.symbol_code = info_text.at(start + 9);
|
||||
|
||||
pos.latitude = parse_lat_str_cmp(lat_str);
|
||||
pos.longitude = parse_lng_str_cmp(lng_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
payload_size = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t DIGIPEATER_START = 14;
|
||||
const uint8_t SOURCE_START = 7;
|
||||
const uint8_t DESTINATION_START = 0;
|
||||
const uint8_t ADDRESS_SIZE = 7;
|
||||
|
||||
bool valid_checksum = false;
|
||||
uint8_t payload[256];
|
||||
char address_buffer[15];
|
||||
uint8_t payload_size;
|
||||
Timestamp timestamp_ { };
|
||||
|
||||
float parse_lat_str_cmp(const std::string& lat_str){
|
||||
return 90.0 - ( (lat_str.at(0) - 33) * (91*91*91) + (lat_str.at(1) - 33) * (91*91) + (lat_str.at(2) - 33) * 91 + (lat_str.at(3)) ) / 380926.0;
|
||||
}
|
||||
|
||||
float parse_lng_str_cmp(const std::string& lng_str){
|
||||
return -180.0 + ( (lng_str.at(0) - 33) * (91*91*91) + (lng_str.at(1) - 33) * (91*91) + (lng_str.at(2) - 33) * 91 + (lng_str.at(3)) ) / 190463.0;
|
||||
}
|
||||
|
||||
uint8_t parse_digits(const std::string& str){
|
||||
if(str.at(0) == ' '){
|
||||
return 0;
|
||||
}
|
||||
uint8_t end = str.find_last_not_of(' ') + 1;
|
||||
std::string sub = str.substr(0, end);
|
||||
|
||||
if(!is_digits(sub)){
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return std::stoi(sub);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_digits(const std::string& str){
|
||||
return str.find_last_not_of("0123456789") == std::string::npos;
|
||||
}
|
||||
|
||||
float parse_lat_str(const std::string& lat_str){
|
||||
float lat = 0.0;
|
||||
|
||||
std::string str_lat_deg = lat_str.substr(0, 2);
|
||||
std::string str_lat_min = lat_str.substr(2, 2);
|
||||
std::string str_lat_hund = lat_str.substr(5, 2);
|
||||
std::string dir = lat_str.substr(7, 1);
|
||||
|
||||
uint8_t lat_deg = parse_digits(str_lat_deg);
|
||||
uint8_t lat_min = parse_digits(str_lat_min);
|
||||
uint8_t lat_hund = parse_digits(str_lat_hund);
|
||||
|
||||
lat += lat_deg;
|
||||
lat += (lat_min + (lat_hund/ 100.0))/60.0;
|
||||
|
||||
if(dir.c_str()[0] == 'S'){
|
||||
lat = -lat;
|
||||
}
|
||||
|
||||
return lat;
|
||||
|
||||
}
|
||||
|
||||
float parse_lng_str(std::string& lng_str){
|
||||
float lng = 0.0;
|
||||
|
||||
std::string str_lng_deg = lng_str.substr(0, 3);
|
||||
std::string str_lng_min = lng_str.substr(3, 2);
|
||||
std::string str_lng_hund = lng_str.substr(6, 2);
|
||||
std::string dir = lng_str.substr(8, 1);
|
||||
|
||||
uint8_t lng_deg = parse_digits(str_lng_deg);
|
||||
uint8_t lng_min = parse_digits(str_lng_min);
|
||||
uint8_t lng_hund = parse_digits(str_lng_hund);
|
||||
|
||||
lng += lng_deg;
|
||||
lng += (lng_min + (lng_hund/ 100.0))/60.0;
|
||||
|
||||
if(dir.c_str()[0] == 'W'){
|
||||
lng = -lng;
|
||||
}
|
||||
|
||||
return lng;
|
||||
|
||||
}
|
||||
|
||||
void parse_mic_e_format(aprs::aprs_pos& pos){
|
||||
std::string lat_str = "";
|
||||
std::string lng_str = "";
|
||||
|
||||
bool is_north;
|
||||
bool is_west;
|
||||
uint8_t lng_offset;
|
||||
for(uint8_t i = DESTINATION_START; i < DESTINATION_START + ADDRESS_SIZE - 1; i++){
|
||||
uint8_t ascii = payload[i] >> 1;
|
||||
|
||||
lat_str += get_mic_e_lat_digit(ascii);
|
||||
|
||||
if(i - DESTINATION_START == 3){
|
||||
lat_str += ".";
|
||||
}
|
||||
if(i - DESTINATION_START == 3){
|
||||
is_north = is_mic_e_lat_N(ascii);
|
||||
}
|
||||
if(i - DESTINATION_START == 4){
|
||||
lng_offset = get_mic_e_lng_offset(ascii);
|
||||
}
|
||||
if(i - DESTINATION_START == 5){
|
||||
is_west = is_mic_e_lng_W(ascii);
|
||||
}
|
||||
}
|
||||
if(is_north){
|
||||
lat_str += "N";
|
||||
}
|
||||
else {
|
||||
lat_str += "S";
|
||||
}
|
||||
|
||||
pos.latitude = parse_lat_str(lat_str);
|
||||
|
||||
float lng = 0.0;
|
||||
uint8_t information_start = get_information_start_index() + 1;
|
||||
for(uint8_t i = information_start; i < information_start + 3 && i < payload_size - 2; i++){
|
||||
uint8_t ascii = payload[i];
|
||||
|
||||
if(i - information_start == 0){ //deg
|
||||
ascii -=28;
|
||||
ascii += lng_offset;
|
||||
|
||||
if(ascii >= 180 && ascii <= 189){
|
||||
ascii -= 80;
|
||||
}
|
||||
else if (ascii >= 190 && ascii <= 199){
|
||||
ascii -= 190;
|
||||
}
|
||||
|
||||
lng += ascii;
|
||||
}
|
||||
else if(i - information_start == 1){ //min
|
||||
ascii -= 28;
|
||||
if(ascii >= 60){
|
||||
ascii -= 60;
|
||||
}
|
||||
lng += ascii/60.0;
|
||||
}
|
||||
else if(i - information_start == 2){ //hundredth minutes
|
||||
ascii -= 28;
|
||||
|
||||
lng += (ascii/100.0)/60.0;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_west){
|
||||
lng = -lng;
|
||||
}
|
||||
|
||||
pos.longitude = lng;
|
||||
|
||||
}
|
||||
|
||||
uint8_t get_mic_e_lat_digit(uint8_t ascii){
|
||||
if(ascii >= '0' && ascii <= '9'){
|
||||
return ascii;
|
||||
}
|
||||
if(ascii >= 'A' && ascii <= 'J'){
|
||||
return ascii - 17;
|
||||
}
|
||||
if(ascii >= 'P' && ascii <='Y'){
|
||||
return ascii - 32;
|
||||
}
|
||||
if(ascii == 'K' || ascii == 'L' || ascii == 'Z'){
|
||||
return ' ';
|
||||
}
|
||||
|
||||
return '\0';
|
||||
}
|
||||
|
||||
bool is_mic_e_lat_N(uint8_t ascii){
|
||||
if(ascii >= 'P' && ascii <='Z'){
|
||||
return true;
|
||||
}
|
||||
return false; //not technical definition, but the other case is invalid
|
||||
}
|
||||
|
||||
bool is_mic_e_lng_W(uint8_t ascii){
|
||||
if(ascii >= 'P' && ascii <='Z'){
|
||||
return true;
|
||||
}
|
||||
return false; //not technical definition, but the other case is invalid
|
||||
}
|
||||
|
||||
uint8_t get_mic_e_lng_offset(uint8_t ascii){
|
||||
if(ascii >= 'P' && ascii <='Z'){
|
||||
return 100;
|
||||
}
|
||||
return 0; //not technical definition, but the other case is invalid
|
||||
}
|
||||
|
||||
bool parse_address(uint8_t start, ADDRESS_TYPE address_type){
|
||||
uint8_t byte = 0;
|
||||
uint8_t has_more = false;
|
||||
uint8_t ssid = 0;
|
||||
uint8_t buffer_index = 0;
|
||||
|
||||
for(uint8_t i = start; i < start + ADDRESS_SIZE && i < payload_size - 2; i++){
|
||||
byte = payload[i];
|
||||
|
||||
if(i - start == 6){
|
||||
has_more = (byte & 0x1) == 0;
|
||||
ssid = (byte >> 1) & 0x0F;
|
||||
|
||||
if(ssid != 0 || address_type == REPEATER){
|
||||
address_buffer[buffer_index++] = '-';
|
||||
|
||||
if(ssid < 10){
|
||||
address_buffer[buffer_index++] = '0' + ssid;
|
||||
address_buffer[buffer_index++] = '\0';
|
||||
}
|
||||
else {
|
||||
address_buffer[buffer_index++] = '1';
|
||||
address_buffer[buffer_index++] = '0' + ssid - 10;
|
||||
address_buffer[buffer_index++] = '\0';
|
||||
}
|
||||
}
|
||||
else {
|
||||
address_buffer[buffer_index++] = '\0';
|
||||
}
|
||||
}
|
||||
else {
|
||||
byte >>= 1;
|
||||
|
||||
if(byte != ' '){
|
||||
address_buffer[buffer_index++] = byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return has_more;
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace aprs */
|
||||
|
||||
#endif/*__APRS_PACKET_H__*/
|
@ -36,6 +36,7 @@
|
||||
#include "adsb_frame.hpp"
|
||||
#include "ert_packet.hpp"
|
||||
#include "pocsag_packet.hpp"
|
||||
#include "aprs_packet.hpp"
|
||||
#include "sonde_packet.hpp"
|
||||
#include "tpms_packet.hpp"
|
||||
#include "jammer.hpp"
|
||||
@ -111,6 +112,8 @@ public:
|
||||
AudioLevelReport = 51,
|
||||
CodedSquelch = 52,
|
||||
AudioSpectrum = 53,
|
||||
APRSPacket = 54,
|
||||
APRSRxConfigure = 55,
|
||||
MAX
|
||||
};
|
||||
|
||||
@ -725,6 +728,18 @@ public:
|
||||
const bool trigger_word;
|
||||
};
|
||||
|
||||
class APRSRxConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr APRSRxConfigureMessage(
|
||||
const uint32_t baudrate
|
||||
) : Message { ID::APRSRxConfigure },
|
||||
baudrate(baudrate)
|
||||
{
|
||||
}
|
||||
|
||||
const uint32_t baudrate;
|
||||
};
|
||||
|
||||
class BTLERxConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr BTLERxConfigureMessage(
|
||||
@ -1002,6 +1017,19 @@ public:
|
||||
const bool phase;
|
||||
};
|
||||
|
||||
class APRSPacketMessage : public Message {
|
||||
public:
|
||||
constexpr APRSPacketMessage(
|
||||
const aprs::APRSPacket& packet
|
||||
) : Message { ID::APRSPacket },
|
||||
packet { packet }
|
||||
{
|
||||
}
|
||||
|
||||
aprs::APRSPacket packet;
|
||||
};
|
||||
|
||||
|
||||
class ADSBConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr ADSBConfigureMessage(
|
||||
|
@ -77,6 +77,7 @@ private:
|
||||
constexpr image_tag_t image_tag_acars { 'P', 'A', 'C', 'A' };
|
||||
constexpr image_tag_t image_tag_adsb_rx { 'P', 'A', 'D', 'R' };
|
||||
constexpr image_tag_t image_tag_afsk_rx { 'P', 'A', 'F', 'R' };
|
||||
constexpr image_tag_t image_tag_aprs_rx { 'P', 'A', 'P', 'R' };
|
||||
constexpr image_tag_t image_tag_btle_rx { 'P', 'B', 'T', 'R' };
|
||||
constexpr image_tag_t image_tag_nrf_rx { 'P', 'N', 'R', 'R' };
|
||||
constexpr image_tag_t image_tag_ais { 'P', 'A', 'I', 'S' };
|
||||
|
@ -597,11 +597,14 @@ Console::Console(
|
||||
void Console::clear(bool clear_buffer = false) {
|
||||
if(clear_buffer)
|
||||
buffer.clear();
|
||||
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black()
|
||||
);
|
||||
|
||||
if(!hidden() && visible()){
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black()
|
||||
);
|
||||
}
|
||||
|
||||
pos = { 0, 0 };
|
||||
}
|
||||
|
||||
@ -648,8 +651,7 @@ void Console::write(std::string message) {
|
||||
}
|
||||
|
||||
void Console::writeln(std::string message) {
|
||||
write(message);
|
||||
write("\n");
|
||||
write(message + "\n");
|
||||
//crlf();
|
||||
}
|
||||
|
||||
@ -658,18 +660,31 @@ void Console::paint(Painter&) {
|
||||
}
|
||||
|
||||
void Console::on_show() {
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
display.scroll_set_position(0);
|
||||
enable_scrolling(true);
|
||||
clear();
|
||||
//visible = true;
|
||||
}
|
||||
|
||||
bool Console::scrolling_enabled = false;
|
||||
|
||||
void Console::enable_scrolling(bool enable){
|
||||
if(enable){
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
display.scroll_set_position(0);
|
||||
scrolling_enabled = true;
|
||||
}
|
||||
else {
|
||||
display.scroll_disable();
|
||||
scrolling_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Console::on_hide() {
|
||||
/* TODO: Clear region to eliminate brief flash of content at un-shifted
|
||||
* position?
|
||||
*/
|
||||
display.scroll_disable();
|
||||
enable_scrolling(false);
|
||||
//visible = false;
|
||||
}
|
||||
|
||||
@ -682,6 +697,9 @@ void Console::crlf() {
|
||||
pos = { 0, pos.y() + line_height };
|
||||
const int32_t y_excess = pos.y() + line_height - sr.height();
|
||||
if( y_excess > 0 ) {
|
||||
if(!scrolling_enabled){
|
||||
enable_scrolling(true);
|
||||
}
|
||||
display.scroll(-y_excess);
|
||||
pos = { pos.x(), pos.y() - y_excess };
|
||||
|
||||
@ -1293,9 +1311,9 @@ size_t OptionsField::selected_index_value() const {
|
||||
return options[selected_index_].second;
|
||||
}
|
||||
|
||||
void OptionsField::set_selected_index(const size_t new_index) {
|
||||
void OptionsField::set_selected_index(const size_t new_index, bool trigger_change) {
|
||||
if( new_index < options.size() ) {
|
||||
if( new_index != selected_index() ) {
|
||||
if( new_index != selected_index() || trigger_change) {
|
||||
selected_index_ = new_index;
|
||||
if( on_change ) {
|
||||
on_change(selected_index(), options[selected_index()].second);
|
||||
|
@ -325,6 +325,7 @@ public:
|
||||
|
||||
void paint(Painter&) override;
|
||||
|
||||
void enable_scrolling(bool enable);
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
@ -332,6 +333,7 @@ private:
|
||||
//bool visible = false;
|
||||
Point pos { 0, 0 };
|
||||
std::string buffer { };
|
||||
static bool scrolling_enabled;
|
||||
|
||||
void crlf();
|
||||
};
|
||||
@ -545,7 +547,7 @@ public:
|
||||
|
||||
size_t selected_index() const;
|
||||
size_t selected_index_value() const;
|
||||
void set_selected_index(const size_t new_index);
|
||||
void set_selected_index(const size_t new_index, bool trigger_change = true);
|
||||
|
||||
void set_by_value(value_t v);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user