mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-12 01:29:29 +00:00
SYNC
This commit is contained in:
@@ -118,11 +118,11 @@ CSRC = $(PORTSRC) \
|
||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
CPPSRC = main.cpp \
|
||||
irq_ipc.cpp \
|
||||
irq_lcd_frame.cpp \
|
||||
irq_controls.cpp \
|
||||
irq_rtc.cpp \
|
||||
event.cpp \
|
||||
event_m0.cpp \
|
||||
message_queue.cpp \
|
||||
hackrf_hal.cpp \
|
||||
portapack.cpp \
|
||||
@@ -136,7 +136,6 @@ CPPSRC = main.cpp \
|
||||
wm8731.cpp \
|
||||
radio.cpp \
|
||||
baseband_cpld.cpp \
|
||||
baseband_sgpio.cpp \
|
||||
tuning.cpp \
|
||||
rf_path.cpp \
|
||||
rffc507x.cpp \
|
||||
@@ -149,26 +148,20 @@ CPPSRC = main.cpp \
|
||||
encoder.cpp \
|
||||
lcd_ili9341.cpp \
|
||||
ui.cpp \
|
||||
ui_alphanum.cpp \
|
||||
ui_text.cpp \
|
||||
ui_widget.cpp \
|
||||
ui_painter.cpp \
|
||||
ui_focus.cpp \
|
||||
ui_navigation.cpp \
|
||||
ui_menu.cpp \
|
||||
ui_about.cpp \
|
||||
ui_rssi.cpp \
|
||||
ui_channel.cpp \
|
||||
ui_audio.cpp \
|
||||
ui_font_fixed_8x16.cpp \
|
||||
ui_setup.cpp \
|
||||
ui_debug.cpp \
|
||||
ui_rds.cpp \
|
||||
ui_lcr.cpp \
|
||||
ui_whistle.cpp \
|
||||
ui_jammer.cpp \
|
||||
ui_afsksetup.cpp \
|
||||
ui_baseband_stats_view.cpp \
|
||||
ui_sd_card_status_view.cpp \
|
||||
ui_console.cpp \
|
||||
ui_receiver.cpp \
|
||||
ui_spectrum.cpp \
|
||||
@@ -177,10 +170,24 @@ CPPSRC = main.cpp \
|
||||
ui_sigfrx.cpp \
|
||||
ui_xylos.cpp \
|
||||
ui_numbers.cpp \
|
||||
recent_entries.cpp \
|
||||
receiver_model.cpp \
|
||||
transmitter_model.cpp \
|
||||
spectrum_color_lut.cpp \
|
||||
analog_audio_app.cpp \
|
||||
ais_baseband.cpp \
|
||||
../commom/ais_packet.cpp \
|
||||
ais_app.cpp \
|
||||
tpms_app.cpp \
|
||||
ert_app.cpp \
|
||||
../common/ert_packet.cpp \
|
||||
spectrum_analysis_app.cpp \
|
||||
sd_card.cpp \
|
||||
file.cpp \
|
||||
log_file.cpp \
|
||||
manchester.cpp \
|
||||
string_format.cpp \
|
||||
temperature_logger.cpp \
|
||||
../common/utility.cpp \
|
||||
../common/chibios_cpp.cpp \
|
||||
../common/debug.cpp \
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ais_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ais {
|
||||
namespace format {
|
||||
|
||||
static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) {
|
||||
const auto suffix = suffixes[(normalized < 0) ? 0 : 1];
|
||||
const uint32_t normalized_abs = std::abs(normalized);
|
||||
const uint32_t t = (normalized_abs * 5) / 3;
|
||||
const uint32_t degrees = t / (100 * 10000);
|
||||
const uint32_t fraction = t % (100 * 10000);
|
||||
return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix;
|
||||
}
|
||||
|
||||
static std::string latitude(const Latitude value) {
|
||||
if( value.is_not_available() ) {
|
||||
return "not available";
|
||||
} else if( value.is_valid() ) {
|
||||
return latlon_abs_normalized(value.normalized(), "SN");
|
||||
} else {
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string longitude(const Longitude value) {
|
||||
if( value.is_not_available() ) {
|
||||
return "not available";
|
||||
} else if( value.is_valid() ) {
|
||||
return latlon_abs_normalized(value.normalized(), "WE");
|
||||
} else {
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string latlon(const Latitude latitude, const Longitude longitude) {
|
||||
if( latitude.is_valid() && longitude.is_valid() ) {
|
||||
return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE");
|
||||
} else if( latitude.is_not_available() && longitude.is_not_available() ) {
|
||||
return "not available";
|
||||
} else {
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string mmsi(
|
||||
const ais::MMSI& mmsi
|
||||
) {
|
||||
return to_string_dec_uint(mmsi, 9);
|
||||
}
|
||||
|
||||
static std::string datetime(
|
||||
const ais::DateTime& datetime
|
||||
) {
|
||||
return to_string_dec_uint(datetime.year, 4, '0') + "/" +
|
||||
to_string_dec_uint(datetime.month, 2, '0') + "/" +
|
||||
to_string_dec_uint(datetime.day, 2, '0') + " " +
|
||||
to_string_dec_uint(datetime.hour, 2, '0') + ":" +
|
||||
to_string_dec_uint(datetime.minute, 2, '0') + ":" +
|
||||
to_string_dec_uint(datetime.second, 2, '0');
|
||||
}
|
||||
|
||||
static std::string navigational_status(const unsigned int value) {
|
||||
switch(value) {
|
||||
case 0: return "under way w/engine";
|
||||
case 1: return "at anchor";
|
||||
case 2: return "not under command";
|
||||
case 3: return "restricted maneuv";
|
||||
case 4: return "constrained draught";
|
||||
case 5: return "moored";
|
||||
case 6: return "aground";
|
||||
case 7: return "fishing";
|
||||
case 8: return "sailing";
|
||||
case 9: case 10: case 13: return "reserved";
|
||||
case 11: return "towing astern";
|
||||
case 12: return "towing ahead/along";
|
||||
case 14: return "SART/MOB/EPIRB";
|
||||
case 15: return "undefined";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string rate_of_turn(const RateOfTurn value) {
|
||||
switch(value) {
|
||||
case -128: return "not available";
|
||||
case -127: return "left >5 deg/30sec";
|
||||
case 0: return "0 deg/min";
|
||||
case 127: return "right >5 deg/30sec";
|
||||
default:
|
||||
{
|
||||
std::string result = (value < 0) ? "left " : "right ";
|
||||
const float value_deg_sqrt = value / 4.733f;
|
||||
const int32_t value_deg = value_deg_sqrt * value_deg_sqrt;
|
||||
result += to_string_dec_uint(value_deg);
|
||||
result += " deg/min";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string speed_over_ground(const SpeedOverGround value) {
|
||||
if( value == 1023 ) {
|
||||
return "not available";
|
||||
} else if( value == 1022 ) {
|
||||
return ">= 102.2 knots";
|
||||
} else {
|
||||
return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " knots";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string course_over_ground(const CourseOverGround value) {
|
||||
if( value > 3600 ) {
|
||||
return "invalid";
|
||||
} else if( value == 3600 ) {
|
||||
return "not available";
|
||||
} else {
|
||||
return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " deg";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string true_heading(const TrueHeading value) {
|
||||
if( value == 511 ) {
|
||||
return "not available";
|
||||
} else if( value > 359 ) {
|
||||
return "invalid";
|
||||
} else {
|
||||
return to_string_dec_uint(value) + " deg";
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
} /* namespace ais */
|
||||
|
||||
void AISLogger::on_packet(const ais::Packet& packet) {
|
||||
// TODO: Unstuff here, not in baseband!
|
||||
if( log_file.is_ready() ) {
|
||||
std::string entry;
|
||||
entry.reserve((packet.length() + 3) / 4);
|
||||
|
||||
for(size_t i=0; i<packet.length(); i+=4) {
|
||||
const auto nibble = packet.read(i, 4);
|
||||
entry += (nibble >= 10) ? ('W' + nibble) : ('0' + nibble);
|
||||
}
|
||||
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
void AISRecentEntry::update(const ais::Packet& packet) {
|
||||
received_count++;
|
||||
|
||||
switch(packet.message_id()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
navigational_status = packet.read(38, 4);
|
||||
last_position.rate_of_turn = packet.read(42, 8);
|
||||
last_position.speed_over_ground = packet.read(50, 10);
|
||||
last_position.timestamp = packet.received_at();
|
||||
last_position.latitude = packet.latitude(89);
|
||||
last_position.longitude = packet.longitude(61);
|
||||
last_position.course_over_ground = packet.read(116, 12);
|
||||
last_position.true_heading = packet.read(128, 9);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
last_position.timestamp = packet.received_at();
|
||||
last_position.latitude = packet.latitude(107);
|
||||
last_position.longitude = packet.longitude(79);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
call_sign = packet.text(70, 7);
|
||||
name = packet.text(112, 20);
|
||||
destination = packet.text(302, 20);
|
||||
break;
|
||||
|
||||
case 21:
|
||||
name = packet.text(43, 20);
|
||||
last_position.timestamp = packet.received_at();
|
||||
last_position.latitude = packet.latitude(192);
|
||||
last_position.longitude = packet.longitude(164);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const std::array<std::pair<std::string, size_t>, 2> ais_columns { {
|
||||
{ "MMSI", 9 },
|
||||
{ "Name/Call", 20 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<AISRecentEntries>::draw_header(
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style
|
||||
) {
|
||||
auto x = 0;
|
||||
for(const auto& column : ais_columns) {
|
||||
const auto width = column.second;
|
||||
auto text = column.first;
|
||||
if( width > text.length() ) {
|
||||
text.append(width - text.length(), ' ');
|
||||
}
|
||||
|
||||
painter.draw_string({ x, target_rect.pos.y }, style, text);
|
||||
x += (width * 8) + 8;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<AISRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style,
|
||||
const bool is_selected
|
||||
) {
|
||||
const auto& draw_style = is_selected ? style.invert() : style;
|
||||
|
||||
std::string line = ais::format::mmsi(entry.mmsi) + " ";
|
||||
if( !entry.name.empty() ) {
|
||||
line += entry.name;
|
||||
} else {
|
||||
line += entry.call_sign;
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.pos, draw_style, line);
|
||||
}
|
||||
|
||||
AISRecentEntryDetailView::AISRecentEntryDetailView() {
|
||||
add_children({ {
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_done.on_select = [this](const ui::Button&) {
|
||||
if( this->on_close ) {
|
||||
this->on_close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
Rect AISRecentEntryDetailView::draw_field(
|
||||
Painter& painter,
|
||||
const Rect& draw_rect,
|
||||
const Style& style,
|
||||
const std::string& label,
|
||||
const std::string& value
|
||||
) {
|
||||
const int label_length_max = 4;
|
||||
|
||||
painter.draw_string(Point { draw_rect.left(), draw_rect.top() }, style, label);
|
||||
painter.draw_string(Point { draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top() }, style, value);
|
||||
|
||||
return { draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height() };
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::paint(Painter& painter) {
|
||||
View::paint(painter);
|
||||
|
||||
const auto s = style();
|
||||
const auto rect = screen_rect();
|
||||
|
||||
auto field_rect = Rect { rect.left(), rect.top() + 16, rect.width(), 16 };
|
||||
|
||||
field_rect = draw_field(painter, field_rect, s, "MMSI", ais::format::mmsi(entry_.mmsi));
|
||||
field_rect = draw_field(painter, field_rect, s, "Name", entry_.name);
|
||||
field_rect = draw_field(painter, field_rect, s, "Call", entry_.call_sign);
|
||||
field_rect = draw_field(painter, field_rect, s, "Dest", entry_.destination);
|
||||
field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp));
|
||||
field_rect = draw_field(painter, field_rect, s, "Pos ", ais::format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
|
||||
field_rect = draw_field(painter, field_rect, s, "Stat", ais::format::navigational_status(entry_.navigational_status));
|
||||
field_rect = draw_field(painter, field_rect, s, "RoT ", ais::format::rate_of_turn(entry_.last_position.rate_of_turn));
|
||||
field_rect = draw_field(painter, field_rect, s, "SoG ", ais::format::speed_over_ground(entry_.last_position.speed_over_ground));
|
||||
field_rect = draw_field(painter, field_rect, s, "CoG ", ais::format::course_over_ground(entry_.last_position.course_over_ground));
|
||||
field_rect = draw_field(painter, field_rect, s, "Head", ais::format::true_heading(entry_.last_position.true_heading));
|
||||
field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count));
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::set_entry(const AISRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
AISAppView::AISAppView(NavigationView&) {
|
||||
add_children({ {
|
||||
&label_channel,
|
||||
&options_channel,
|
||||
&recent_entries_view,
|
||||
&recent_entry_detail_view,
|
||||
} });
|
||||
|
||||
recent_entry_detail_view.hidden(true);
|
||||
|
||||
EventDispatcher::message_map().register_handler(Message::ID::AISPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const AISPacketMessage*>(p);
|
||||
const ais::Packet packet { message->packet };
|
||||
if( packet.is_valid() ) {
|
||||
this->on_packet(packet);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
this->on_frequency_changed(v);
|
||||
};
|
||||
options_channel.set_by_value(162025000);
|
||||
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = 3,
|
||||
.sampling_rate = 2457600,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_rf_amp(false);
|
||||
receiver_model.set_lna(32);
|
||||
receiver_model.set_vga(32);
|
||||
receiver_model.enable();
|
||||
|
||||
recent_entries_view.on_select = [this](const AISRecentEntry& entry) {
|
||||
this->on_show_detail(entry);
|
||||
};
|
||||
recent_entry_detail_view.on_close = [this]() {
|
||||
this->on_show_list();
|
||||
};
|
||||
}
|
||||
|
||||
AISAppView::~AISAppView() {
|
||||
receiver_model.disable();
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::AISPacket);
|
||||
}
|
||||
|
||||
void AISAppView::focus() {
|
||||
options_channel.focus();
|
||||
}
|
||||
|
||||
void AISAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
const Rect content_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
|
||||
recent_entries_view.set_parent_rect(content_rect);
|
||||
recent_entry_detail_view.set_parent_rect(content_rect);
|
||||
}
|
||||
|
||||
void AISAppView::on_packet(const ais::Packet& packet) {
|
||||
logger.on_packet(packet);
|
||||
const auto updated_entry = recent.on_packet(packet.source_id(), packet);
|
||||
recent_entries_view.set_dirty();
|
||||
|
||||
// TODO: Crude hack, should be a more formal listener arrangement...
|
||||
if( updated_entry.key() == recent_entry_detail_view.entry().key() ) {
|
||||
recent_entry_detail_view.set_entry(updated_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void AISAppView::on_show_list() {
|
||||
recent_entries_view.hidden(false);
|
||||
recent_entry_detail_view.hidden(true);
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
void AISAppView::on_show_detail(const AISRecentEntry& entry) {
|
||||
recent_entries_view.hidden(true);
|
||||
recent_entry_detail_view.hidden(false);
|
||||
recent_entry_detail_view.set_entry(entry);
|
||||
recent_entry_detail_view.focus();
|
||||
}
|
||||
|
||||
void AISAppView::on_frequency_changed(const uint32_t new_frequency) {
|
||||
receiver_model.set_tuning_frequency(new_frequency);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __AIS_APP_H__
|
||||
#define __AIS_APP_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "ais_packet.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
struct AISPosition {
|
||||
rtc::RTC timestamp { };
|
||||
ais::Latitude latitude;
|
||||
ais::Longitude longitude;
|
||||
ais::RateOfTurn rate_of_turn { -128 };
|
||||
ais::SpeedOverGround speed_over_ground { 1023 };
|
||||
ais::CourseOverGround course_over_ground { 3600 };
|
||||
ais::TrueHeading true_heading { 511 };
|
||||
};
|
||||
|
||||
struct AISRecentEntry {
|
||||
using Key = ais::MMSI;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffff;
|
||||
|
||||
ais::MMSI mmsi;
|
||||
std::string name;
|
||||
std::string call_sign;
|
||||
std::string destination;
|
||||
AISPosition last_position;
|
||||
size_t received_count;
|
||||
int8_t navigational_status;
|
||||
|
||||
AISRecentEntry(
|
||||
) : AISRecentEntry { 0 }
|
||||
{
|
||||
}
|
||||
|
||||
AISRecentEntry(
|
||||
const ais::MMSI& mmsi
|
||||
) : mmsi { mmsi },
|
||||
last_position { },
|
||||
received_count { 0 },
|
||||
navigational_status { -1 }
|
||||
{
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return mmsi;
|
||||
}
|
||||
|
||||
void update(const ais::Packet& packet);
|
||||
};
|
||||
|
||||
using AISRecentEntries = RecentEntries<ais::Packet, AISRecentEntry>;
|
||||
|
||||
class AISLogger {
|
||||
public:
|
||||
void on_packet(const ais::Packet& packet);
|
||||
|
||||
private:
|
||||
LogFile log_file { "ais.txt" };
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
|
||||
|
||||
class AISRecentEntryDetailView : public View {
|
||||
public:
|
||||
std::function<void(void)> on_close;
|
||||
|
||||
AISRecentEntryDetailView();
|
||||
|
||||
void set_entry(const AISRecentEntry& new_entry);
|
||||
const AISRecentEntry& entry() const { return entry_; };
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
|
||||
private:
|
||||
AISRecentEntry entry_;
|
||||
|
||||
Button button_done {
|
||||
{ 72, 216, 96, 24 },
|
||||
"Done"
|
||||
};
|
||||
|
||||
Rect draw_field(
|
||||
Painter& painter,
|
||||
const Rect& draw_rect,
|
||||
const Style& style,
|
||||
const std::string& label,
|
||||
const std::string& value
|
||||
);
|
||||
};
|
||||
|
||||
class AISAppView : public View {
|
||||
public:
|
||||
AISAppView(NavigationView& nav);
|
||||
~AISAppView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
// Prevent painting of region covered entirely by a child.
|
||||
// TODO: Add flag to View that specifies view does not need to be cleared before painting.
|
||||
void paint(Painter&) override { };
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "AIS"; };
|
||||
|
||||
private:
|
||||
AISRecentEntries recent;
|
||||
AISLogger logger;
|
||||
|
||||
AISRecentEntriesView recent_entries_view { recent };
|
||||
AISRecentEntryDetailView recent_entry_detail_view;
|
||||
|
||||
static constexpr auto header_height = 1 * 16;
|
||||
|
||||
Text label_channel {
|
||||
{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
|
||||
"Ch"
|
||||
};
|
||||
|
||||
OptionsField options_channel {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
3,
|
||||
{
|
||||
{ "87B", 161975000 },
|
||||
{ "88B", 162025000 },
|
||||
}
|
||||
};
|
||||
|
||||
void on_packet(const ais::Packet& packet);
|
||||
void on_show_list();
|
||||
void on_show_detail(const AISRecentEntry& entry);
|
||||
|
||||
void on_frequency_changed(const uint32_t new_frequency);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__AIS_APP_H__*/
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "analog_audio_app.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
AnalogAudioModel::AnalogAudioModel(ReceiverModel::Mode mode) {
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = toUType(mode),
|
||||
.sampling_rate = 3072000,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
|
||||
switch(mode) {
|
||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||
configure_nbfm();
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
configure_wfm();
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
configure_am();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AnalogAudioModel::configure_nbfm() {
|
||||
const NBFMConfigureMessage message {
|
||||
taps_4k25_decim_0,
|
||||
taps_4k25_decim_1,
|
||||
taps_4k25_channel,
|
||||
2500,
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
}
|
||||
|
||||
void AnalogAudioModel::configure_wfm() {
|
||||
const WFMConfigureMessage message {
|
||||
taps_200k_wfm_decim_0,
|
||||
taps_200k_wfm_decim_1,
|
||||
taps_64_lp_156_198,
|
||||
75000,
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
}
|
||||
|
||||
void AnalogAudioModel::configure_am() {
|
||||
const AMConfigureMessage message {
|
||||
taps_6k0_decim_0,
|
||||
taps_6k0_decim_1,
|
||||
taps_6k0_channel,
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
}
|
||||
+21
-34
@@ -19,49 +19,36 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BASEBAND_GPIO_H__
|
||||
#define __BASEBAND_GPIO_H__
|
||||
#ifndef __ANALOG_AUDIO_APP_H__
|
||||
#define __ANALOG_AUDIO_APP_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#include "baseband.hpp"
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
namespace baseband {
|
||||
|
||||
class SGPIO {
|
||||
class AnalogAudioModel {
|
||||
public:
|
||||
void init() {
|
||||
}
|
||||
AnalogAudioModel(ReceiverModel::Mode mode);
|
||||
|
||||
void configure(const Direction direction);
|
||||
private:
|
||||
void configure_nbfm();
|
||||
void configure_wfm();
|
||||
void configure_am();
|
||||
};
|
||||
|
||||
void streaming_enable() {
|
||||
/* TODO: Any reason not to control from general GPIO facility? */
|
||||
LPC_SGPIO->GPIO_OUTREG &= ~(1U << 10);
|
||||
}
|
||||
namespace ui {
|
||||
|
||||
void streaming_disable() {
|
||||
/* TODO: Any reason not to control from general GPIO facility? */
|
||||
LPC_SGPIO->GPIO_OUTREG |= (1U << 10);
|
||||
}
|
||||
|
||||
bool streaming_is_enabled() const {
|
||||
/* TODO: Any reason not to control from general GPIO facility? */
|
||||
return (LPC_SGPIO->GPIO_OUTREG >> 10) & 1;
|
||||
class AnalogAudioView : public spectrum::WaterfallWidget {
|
||||
public:
|
||||
AnalogAudioView(
|
||||
ReceiverModel::Mode mode
|
||||
) : model { mode }
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void disable_all_slice_counters() {
|
||||
set_slice_counter_enables(0);
|
||||
}
|
||||
|
||||
void set_slice_counter_enables(const uint16_t enable_mask) {
|
||||
LPC_SGPIO->CTRL_ENABLE = enable_mask;
|
||||
}
|
||||
AnalogAudioModel model;
|
||||
};
|
||||
|
||||
}
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__BASEBAND_GPIO_H__*/
|
||||
#endif/*__ANALOG_AUDIO_APP_H__*/
|
||||
@@ -1,373 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "baseband_sgpio.hpp"
|
||||
|
||||
#include "baseband.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace baseband {
|
||||
|
||||
/*
|
||||
|
||||
struct PinConfig {
|
||||
P_OUT_CFG p_out_cfg;
|
||||
P_OE_CFG p_oe_cfg { P_OE_CFG::GPIO_OE };
|
||||
|
||||
constexpr SGPIOPinConfig(
|
||||
P_OUT_CFG p_out_cfg
|
||||
) :
|
||||
p_out_cfg(p_out_cfg)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr bool slice_mode_multislice = false;
|
||||
|
||||
static constexpr P_OUT_CFG output_multiplexing_mode =
|
||||
slice_mode_multislice ? P_OUT_CFG::DOUT_DOUTM8C : P_OUT_CFG::DOUT_DOUTM8A;
|
||||
|
||||
static constexpr std::array<PinConfig, 16> pin_config { {
|
||||
[PIN_D0] = { output_multiplexing_mode, SLICE_A },
|
||||
[PIN_D1] = { output_multiplexing_mode, SLICE_I },
|
||||
[PIN_D2] = { output_multiplexing_mode, },
|
||||
[PIN_D3] = { output_multiplexing_mode, },
|
||||
[PIN_D4] = { output_multiplexing_mode, },
|
||||
[PIN_D5] = { output_multiplexing_mode, },
|
||||
[PIN_D6] = { output_multiplexing_mode, },
|
||||
[PIN_D7] = { output_multiplexing_mode, },
|
||||
[PIN_CLKIN] = { P_OUT_CFG::DOUT_DOUTM1, },
|
||||
[PIN_CAPTURE] = { P_OUT_CFG::DOUT_DOUTM1, },
|
||||
[PIN_DISABLE] = { P_OUT_CFG::GPIO_OUT, },
|
||||
[PIN_DIRECTION] = { P_OUT_CFG::GPIO_OUT, },
|
||||
[PIN_INVERT] = { P_OUT_CFG::GPIO_OUT, },
|
||||
[PIN_DECIM0] = { P_OUT_CFG::GPIO_OUT, },
|
||||
[PIN_DECIM1] = { P_OUT_CFG::DOUT_DOUTM1, },
|
||||
[PIN_DECIM2] = { P_OUT_CFG::GPIO_OUT, },
|
||||
} };
|
||||
*/
|
||||
/*
|
||||
static constexpr std::array<LPC_SGPIO_OUT_MUX_CFG_Type, 16> out_mux_cfg_receive {
|
||||
{ },
|
||||
};
|
||||
|
||||
static constexpr std::array<LPC_SGPIO_OUT_MUX_CFG_Type, 16> out_mux_cfg_transmit {
|
||||
{ },
|
||||
};
|
||||
*/
|
||||
|
||||
enum class P_OUT_CFG : uint8_t {
|
||||
DOUT_DOUTM1 = 0x0,
|
||||
DOUT_DOUTM2A = 0x1,
|
||||
DOUT_DOUTM2B = 0x2,
|
||||
DOUT_DOUTM2C = 0x3,
|
||||
GPIO_OUT = 0x4,
|
||||
DOUT_DOUTM4A = 0x5,
|
||||
DOUT_DOUTM4B = 0x6,
|
||||
DOUT_DOUTM4C = 0x7,
|
||||
CLK_OUT = 0x8,
|
||||
DOUT_DOUTM8A = 0x9,
|
||||
DOUT_DOUTM8B = 0xa,
|
||||
DOUT_DOUTM8C = 0xb,
|
||||
};
|
||||
|
||||
enum class P_OE_CFG : uint8_t {
|
||||
GPIO_OE = 0x0,
|
||||
DOUT_OEM1 = 0x4,
|
||||
DOUT_OEM2 = 0x5,
|
||||
DOUT_OEM4 = 0x6,
|
||||
DOUT_OEM8 = 0x7,
|
||||
};
|
||||
|
||||
enum class CONCAT_ORDER : uint8_t {
|
||||
SELF_LOOP = 0x0,
|
||||
TWO_SLICES = 0x1,
|
||||
FOUR_SLICES = 0x2,
|
||||
EIGHT_SLICES = 0x3,
|
||||
};
|
||||
|
||||
enum class CONCAT_ENABLE : uint8_t {
|
||||
EXTERNAL_DATA_PIN = 0x0,
|
||||
CONCATENATE_DATA = 0x1,
|
||||
};
|
||||
|
||||
enum class CLK_CAPTURE_MODE : uint8_t {
|
||||
RISING_CLOCK_EDGE = 0,
|
||||
FALLING_CLOCK_EDGE = 1,
|
||||
};
|
||||
|
||||
enum class PARALLEL_MODE : uint8_t {
|
||||
SHIFT_1_BIT_PER_CLOCK = 0x0,
|
||||
SHIFT_2_BITS_PER_CLOCK = 0x1,
|
||||
SHIFT_4_BITS_PER_CLOCK = 0x2,
|
||||
SHIFT_1_BYTE_PER_CLOCK = 0x3,
|
||||
};
|
||||
|
||||
enum {
|
||||
PIN_D0 = 0,
|
||||
PIN_D1 = 1,
|
||||
PIN_D2 = 2,
|
||||
PIN_D3 = 3,
|
||||
PIN_D4 = 4,
|
||||
PIN_D5 = 5,
|
||||
PIN_D6 = 6,
|
||||
PIN_D7 = 7,
|
||||
PIN_CLKIN = 8,
|
||||
PIN_CAPTURE = 9,
|
||||
PIN_DISABLE = 10,
|
||||
PIN_DIRECTION = 11,
|
||||
PIN_INVERT = 12,
|
||||
PIN_DECIM0 = 13,
|
||||
PIN_DECIM1 = 14,
|
||||
PIN_DECIM2 = 15,
|
||||
};
|
||||
|
||||
enum class Slice : uint8_t {
|
||||
A = 0,
|
||||
B = 1,
|
||||
C = 2,
|
||||
D = 3,
|
||||
E = 4,
|
||||
F = 5,
|
||||
G = 6,
|
||||
H = 7,
|
||||
I = 8,
|
||||
J = 9,
|
||||
K = 10,
|
||||
L = 11,
|
||||
M = 12,
|
||||
N = 13,
|
||||
O = 14,
|
||||
P = 15,
|
||||
};
|
||||
|
||||
constexpr bool slice_mode_multislice = false;
|
||||
|
||||
constexpr uint8_t pos_count_multi_slice = 0x1f;
|
||||
constexpr uint8_t pos_count_single_slice = 0x03;
|
||||
|
||||
constexpr Slice slice_order[] {
|
||||
Slice::A,
|
||||
Slice::I,
|
||||
Slice::E,
|
||||
Slice::J,
|
||||
Slice::C,
|
||||
Slice::K,
|
||||
Slice::F,
|
||||
Slice::L,
|
||||
Slice::B,
|
||||
Slice::M,
|
||||
Slice::G,
|
||||
Slice::N,
|
||||
Slice::D,
|
||||
Slice::O,
|
||||
Slice::H,
|
||||
Slice::P,
|
||||
};
|
||||
|
||||
constexpr uint32_t gpio_outreg(const Direction direction) {
|
||||
return ((direction == Direction::Transmit) ? (1U << 11) : 0U) | (1U << 10);
|
||||
}
|
||||
|
||||
constexpr uint32_t gpio_oenreg(const Direction direction) {
|
||||
return
|
||||
(0U << PIN_DECIM2)
|
||||
| (1U << PIN_DECIM1)
|
||||
| (0U << PIN_DECIM0)
|
||||
| (0U << PIN_INVERT)
|
||||
| (1U << PIN_DIRECTION)
|
||||
| (1U << PIN_DISABLE)
|
||||
| (0U << PIN_CAPTURE)
|
||||
| (0U << PIN_CLKIN)
|
||||
| ((direction == Direction::Transmit) ? 0xffU : 0x00U)
|
||||
;
|
||||
}
|
||||
|
||||
constexpr uint32_t out_mux_cfg(const P_OUT_CFG out, const P_OE_CFG oe) {
|
||||
return
|
||||
(toUType(out) << 0)
|
||||
| (toUType(oe) << 4)
|
||||
;
|
||||
}
|
||||
|
||||
constexpr uint32_t data_sgpio_mux_cfg(
|
||||
const CONCAT_ENABLE concat_enable,
|
||||
const CONCAT_ORDER concat_order
|
||||
) {
|
||||
return
|
||||
(1U << 0)
|
||||
| (0U << 1)
|
||||
| (0U << 3)
|
||||
| (3U << 5)
|
||||
| (1U << 7)
|
||||
| (0U << 9)
|
||||
| (toUType(concat_enable) << 11)
|
||||
| (toUType(concat_order) << 12)
|
||||
;
|
||||
}
|
||||
|
||||
constexpr uint32_t data_slice_mux_cfg(
|
||||
const PARALLEL_MODE parallel_mode,
|
||||
const CLK_CAPTURE_MODE clk_capture_mode
|
||||
) {
|
||||
return
|
||||
(0U << 0)
|
||||
| (toUType(clk_capture_mode) << 1)
|
||||
| (1U << 2)
|
||||
| (0U << 3)
|
||||
| (0U << 4)
|
||||
| (toUType(parallel_mode) << 6)
|
||||
| (0U << 8)
|
||||
;
|
||||
}
|
||||
|
||||
constexpr uint32_t pos(
|
||||
const uint32_t pos,
|
||||
const uint32_t pos_reset
|
||||
) {
|
||||
return
|
||||
(pos << 0)
|
||||
| (pos_reset << 8)
|
||||
;
|
||||
}
|
||||
constexpr uint32_t data_pos(
|
||||
const bool multi_slice
|
||||
) {
|
||||
return pos(
|
||||
(multi_slice ? pos_count_multi_slice : pos_count_single_slice),
|
||||
(multi_slice ? pos_count_multi_slice : pos_count_single_slice)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr CONCAT_ENABLE data_concat_enable(
|
||||
const bool input_slice,
|
||||
const bool single_slice
|
||||
) {
|
||||
return (input_slice || single_slice)
|
||||
? CONCAT_ENABLE::EXTERNAL_DATA_PIN
|
||||
: CONCAT_ENABLE::CONCATENATE_DATA
|
||||
;
|
||||
}
|
||||
|
||||
constexpr CONCAT_ORDER data_concat_order(
|
||||
const bool input_slice,
|
||||
const bool single_slice
|
||||
) {
|
||||
return (input_slice || single_slice)
|
||||
? CONCAT_ORDER::SELF_LOOP
|
||||
: CONCAT_ORDER::EIGHT_SLICES
|
||||
;
|
||||
}
|
||||
|
||||
constexpr CLK_CAPTURE_MODE data_clk_capture_mode(
|
||||
const Direction direction
|
||||
) {
|
||||
return (direction == Direction::Transmit)
|
||||
? CLK_CAPTURE_MODE::RISING_CLOCK_EDGE
|
||||
: CLK_CAPTURE_MODE::FALLING_CLOCK_EDGE
|
||||
;
|
||||
}
|
||||
|
||||
constexpr P_OUT_CFG data_p_out_cfg(
|
||||
const bool multi_slice
|
||||
) {
|
||||
return (multi_slice)
|
||||
? P_OUT_CFG::DOUT_DOUTM8C
|
||||
: P_OUT_CFG::DOUT_DOUTM8A
|
||||
;
|
||||
}
|
||||
|
||||
void SGPIO::configure(const Direction direction) {
|
||||
disable_all_slice_counters();
|
||||
|
||||
LPC_SGPIO->GPIO_OUTREG = gpio_outreg(direction);
|
||||
LPC_SGPIO->GPIO_OENREG = gpio_oenreg(direction);
|
||||
LPC_SGPIO->OUT_MUX_CFG[ 8] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[ 9] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[10] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[11] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[12] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[13] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[14] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
|
||||
LPC_SGPIO->OUT_MUX_CFG[15] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
|
||||
|
||||
const auto data_out_mux_cfg = out_mux_cfg(data_p_out_cfg(slice_mode_multislice), P_OE_CFG::GPIO_OE);
|
||||
for(size_t i=0; i<8; i++) {
|
||||
LPC_SGPIO->OUT_MUX_CFG[i] = data_out_mux_cfg;
|
||||
}
|
||||
|
||||
const auto slice_gpdma = Slice::H;
|
||||
|
||||
const size_t slice_count = slice_mode_multislice ? 8 : 1;
|
||||
const auto clk_capture_mode = data_clk_capture_mode(direction);
|
||||
const auto single_slice = !slice_mode_multislice;
|
||||
|
||||
uint32_t slice_enable_mask = 0;
|
||||
for(size_t i=0; i<slice_count; i++) {
|
||||
const auto slice = slice_order[i];
|
||||
const auto slice_index = toUType(slice);
|
||||
const auto input_slice = (i == 0) && (direction != Direction::Transmit);
|
||||
const auto concat_order = data_concat_order(input_slice, single_slice);
|
||||
const auto concat_enable = data_concat_enable(input_slice, single_slice);
|
||||
|
||||
LPC_SGPIO->SGPIO_MUX_CFG[slice_index] = data_sgpio_mux_cfg(
|
||||
concat_enable,
|
||||
concat_order
|
||||
);
|
||||
LPC_SGPIO->SLICE_MUX_CFG[slice_index] = data_slice_mux_cfg(
|
||||
PARALLEL_MODE::SHIFT_1_BYTE_PER_CLOCK,
|
||||
clk_capture_mode
|
||||
);
|
||||
|
||||
LPC_SGPIO->PRESET[slice_index] = 0;
|
||||
LPC_SGPIO->COUNT[slice_index] = 0;
|
||||
LPC_SGPIO->POS[slice_index] = data_pos(slice_mode_multislice);
|
||||
LPC_SGPIO->REG[slice_index] = 0;
|
||||
LPC_SGPIO->REG_SS[slice_index] = 0;
|
||||
|
||||
slice_enable_mask |= (1U << slice_index);
|
||||
}
|
||||
|
||||
if( !slice_mode_multislice ) {
|
||||
const auto slice_index = toUType(slice_gpdma);
|
||||
|
||||
LPC_SGPIO->SGPIO_MUX_CFG[slice_index] = data_sgpio_mux_cfg(
|
||||
CONCAT_ENABLE::CONCATENATE_DATA,
|
||||
CONCAT_ORDER::SELF_LOOP
|
||||
);
|
||||
LPC_SGPIO->SLICE_MUX_CFG[slice_index] = data_slice_mux_cfg(
|
||||
PARALLEL_MODE::SHIFT_1_BIT_PER_CLOCK,
|
||||
clk_capture_mode
|
||||
);
|
||||
|
||||
LPC_SGPIO->PRESET[slice_index] = 0;
|
||||
LPC_SGPIO->COUNT[slice_index] = 0;
|
||||
LPC_SGPIO->POS[slice_index] = pos(0x1f, 0x1f);
|
||||
LPC_SGPIO->REG[slice_index] = 0x11111111;
|
||||
LPC_SGPIO->REG_SS[slice_index] = 0x11111111;
|
||||
|
||||
slice_enable_mask |= (1 << slice_index);
|
||||
}
|
||||
|
||||
set_slice_counter_enables(slice_enable_mask);
|
||||
}
|
||||
|
||||
} /* namespace baseband */
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ert_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "manchester.hpp"
|
||||
|
||||
#include "crc.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ert {
|
||||
|
||||
namespace format {
|
||||
|
||||
std::string type(Packet::Type value) {
|
||||
switch(value) {
|
||||
default:
|
||||
case Packet::Type::Unknown: return "???";
|
||||
case Packet::Type::IDM: return "IDM";
|
||||
case Packet::Type::SCM: return "SCM";
|
||||
}
|
||||
}
|
||||
|
||||
std::string id(ID value) {
|
||||
return to_string_dec_uint(value, 10);
|
||||
}
|
||||
|
||||
std::string consumption(Consumption value) {
|
||||
return to_string_dec_uint(value, 10);
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace ert */
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet) {
|
||||
if( log_file.is_ready() ) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors);
|
||||
}
|
||||
}
|
||||
|
||||
void ERTRecentEntry::update(const ert::Packet& packet) {
|
||||
received_count++;
|
||||
|
||||
last_consumption = packet.consumption();
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const std::array<std::pair<std::string, size_t>, 3> ert_columns { {
|
||||
{ "ID", 10 },
|
||||
{ "Consumpt", 10 },
|
||||
{ "Cnt", 3 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<ERTRecentEntries>::draw_header(
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style
|
||||
) {
|
||||
auto x = 0;
|
||||
for(const auto& column : ert_columns) {
|
||||
const auto width = column.second;
|
||||
auto text = column.first;
|
||||
if( width > text.length() ) {
|
||||
text.append(width - text.length(), ' ');
|
||||
}
|
||||
|
||||
painter.draw_string({ x, target_rect.pos.y }, style, text);
|
||||
x += (width * 8) + 8;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<ERTRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style,
|
||||
const bool is_selected
|
||||
) {
|
||||
const auto& draw_style = is_selected ? style.invert() : style;
|
||||
|
||||
std::string line = ert::format::id(entry.id) + " " + ert::format::consumption(entry.last_consumption);
|
||||
|
||||
if( entry.received_count > 999 ) {
|
||||
line += " +++";
|
||||
} else {
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.pos, draw_style, line);
|
||||
}
|
||||
|
||||
ERTAppView::ERTAppView(NavigationView&) {
|
||||
add_children({ {
|
||||
&recent_entries_view,
|
||||
} });
|
||||
|
||||
EventDispatcher::message_map().register_handler(Message::ID::ERTPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const ERTPacketMessage*>(p);
|
||||
const ert::Packet packet { message->type, message->packet };
|
||||
this->on_packet(packet);
|
||||
}
|
||||
);
|
||||
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = 6,
|
||||
.sampling_rate = 4194304,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(2500000);
|
||||
receiver_model.set_rf_amp(false);
|
||||
receiver_model.set_lna(32);
|
||||
receiver_model.set_vga(32);
|
||||
receiver_model.set_tuning_frequency(911600000);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
ERTAppView::~ERTAppView() {
|
||||
receiver_model.disable();
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::ERTPacket);
|
||||
}
|
||||
|
||||
void ERTAppView::focus() {
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
void ERTAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
recent_entries_view.set_parent_rect({ 0, 0, new_parent_rect.width(), new_parent_rect.height() });
|
||||
}
|
||||
|
||||
void ERTAppView::on_packet(const ert::Packet& packet) {
|
||||
logger.on_packet(packet);
|
||||
|
||||
if( packet.crc_ok() ) {
|
||||
recent.on_packet(packet.id(), packet);
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void ERTAppView::on_show_list() {
|
||||
recent_entries_view.hidden(false);
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ERT_APP_H__
|
||||
#define __ERT_APP_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "ert_packet.hpp"
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
struct ERTRecentEntry {
|
||||
using Key = ert::ID;
|
||||
|
||||
// TODO: Is this the right choice of invalid key value?
|
||||
static constexpr Key invalid_key = 0;
|
||||
|
||||
ert::ID id { invalid_key };
|
||||
|
||||
size_t received_count { 0 };
|
||||
|
||||
ert::Consumption last_consumption;
|
||||
|
||||
ERTRecentEntry(
|
||||
const Key& key
|
||||
) : id { key }
|
||||
{
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
void update(const ert::Packet& packet);
|
||||
};
|
||||
|
||||
class ERTLogger {
|
||||
public:
|
||||
void on_packet(const ert::Packet& packet);
|
||||
|
||||
private:
|
||||
LogFile log_file { "ert.txt" };
|
||||
};
|
||||
|
||||
using ERTRecentEntries = RecentEntries<ert::Packet, ERTRecentEntry>;
|
||||
|
||||
namespace ui {
|
||||
|
||||
using ERTRecentEntriesView = RecentEntriesView<ERTRecentEntries>;
|
||||
|
||||
class ERTAppView : public View {
|
||||
public:
|
||||
ERTAppView(NavigationView& nav);
|
||||
~ERTAppView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
// Prevent painting of region covered entirely by a child.
|
||||
// TODO: Add flag to View that specifies view does not need to be cleared before painting.
|
||||
void paint(Painter&) override { };
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "ERT"; };
|
||||
|
||||
private:
|
||||
ERTRecentEntries recent;
|
||||
ERTLogger logger;
|
||||
|
||||
ERTRecentEntriesView recent_entries_view { recent };
|
||||
|
||||
void on_packet(const ert::Packet& packet);
|
||||
void on_show_list();
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__ERT_APP_H__*/
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
#include "message_queue.hpp"
|
||||
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include <array>
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(M4Core_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_APPLICATION);
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
creg::m4txevent::clear();
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MessageHandlerMap EventDispatcher::message_map_;
|
||||
Thread* EventDispatcher::thread_event_loop = nullptr;
|
||||
|
||||
EventDispatcher::EventDispatcher(
|
||||
ui::Widget* const top_widget,
|
||||
ui::Painter& painter,
|
||||
ui::Context& context
|
||||
) : top_widget { top_widget },
|
||||
painter(painter),
|
||||
context(context)
|
||||
{
|
||||
thread_event_loop = chThdSelf();
|
||||
touch_manager.on_event = [this](const ui::TouchEvent event) {
|
||||
this->on_touch_event(event);
|
||||
};
|
||||
}
|
||||
|
||||
void EventDispatcher::run() {
|
||||
creg::m4txevent::enable();
|
||||
|
||||
while(is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
}
|
||||
|
||||
creg::m4txevent::disable();
|
||||
}
|
||||
|
||||
void EventDispatcher::request_stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
void EventDispatcher::set_display_sleep(const bool sleep) {
|
||||
// TODO: Distribute display sleep message more broadly, shut down data generation
|
||||
// on baseband side, since all that data is being discarded during sleep.
|
||||
if( sleep ) {
|
||||
portapack::io.lcd_backlight(false);
|
||||
portapack::display.sleep();
|
||||
} else {
|
||||
portapack::display.wake();
|
||||
portapack::io.lcd_backlight(true);
|
||||
}
|
||||
display_sleep = sleep;
|
||||
};
|
||||
|
||||
eventmask_t EventDispatcher::wait() {
|
||||
return chEvtWaitAny(ALL_EVENTS);
|
||||
}
|
||||
|
||||
void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
if( events & EVT_MASK_APPLICATION ) {
|
||||
handle_application_queue();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_RTC_TICK ) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_SWITCHES ) {
|
||||
handle_switches();
|
||||
}
|
||||
|
||||
if( !display_sleep ) {
|
||||
if( events & EVT_MASK_LCD_FRAME_SYNC ) {
|
||||
handle_lcd_frame_sync();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_ENCODER ) {
|
||||
handle_encoder();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_TOUCH ) {
|
||||
handle_touch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_application_queue() {
|
||||
std::array<uint8_t, Message::MAX_SIZE> message_buffer;
|
||||
while(Message* const message = shared_memory.application_queue.pop(message_buffer)) {
|
||||
message_map().send(message);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_rtc_tick() {
|
||||
sd_card::poll_inserted();
|
||||
|
||||
portapack::temperature_logger.second_tick();
|
||||
}
|
||||
|
||||
ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent event) {
|
||||
if( !w->hidden() ) {
|
||||
// To achieve reverse depth ordering (last object drawn is
|
||||
// considered "top"), descend first.
|
||||
for(const auto child : w->children()) {
|
||||
const auto touched_widget = touch_widget(child, event);
|
||||
if( touched_widget ) {
|
||||
return touched_widget;
|
||||
}
|
||||
}
|
||||
|
||||
const auto r = w->screen_rect();
|
||||
if( r.contains(event.point) ) {
|
||||
if( w->on_touch(event) ) {
|
||||
// This widget responded. Return it up the call stack.
|
||||
return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
/* TODO: Capture widget receiving the Start event, send Move and
|
||||
* End events to the same widget.
|
||||
*/
|
||||
/* Capture Start widget.
|
||||
* If touch is over Start widget at Move event, then the widget
|
||||
* should be highlighted. If the touch is not over the Start
|
||||
* widget at Move event, widget should un-highlight.
|
||||
* If touch is over Start widget at End event, then the widget
|
||||
* action should occur.
|
||||
*/
|
||||
if( event.type == ui::TouchEvent::Type::Start ) {
|
||||
captured_widget = touch_widget(this->top_widget, event);
|
||||
}
|
||||
|
||||
if( captured_widget ) {
|
||||
captured_widget->on_touch(event);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_lcd_frame_sync() {
|
||||
DisplayFrameSyncMessage message;
|
||||
message_map().send(&message);
|
||||
painter.paint_widget_tree(top_widget);
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_switches() {
|
||||
const auto switches_state = get_switches_state();
|
||||
|
||||
if( display_sleep ) {
|
||||
// Swallow event, wake up display.
|
||||
if( switches_state.any() ) {
|
||||
set_display_sleep(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i=0; i<switches_state.size(); i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
if( switches_state[i] ) {
|
||||
const auto event = static_cast<ui::KeyEvent>(i);
|
||||
if( !event_bubble_key(event) ) {
|
||||
context.focus_manager().update(top_widget, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_encoder() {
|
||||
const uint32_t encoder_now = get_encoder_position();
|
||||
const int32_t delta = static_cast<int32_t>(encoder_now - encoder_last);
|
||||
encoder_last = encoder_now;
|
||||
const auto event = static_cast<ui::EncoderEvent>(delta);
|
||||
event_bubble_encoder(event);
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_touch() {
|
||||
touch_manager.feed(get_touch_frame());
|
||||
}
|
||||
|
||||
bool EventDispatcher::event_bubble_key(const ui::KeyEvent event) {
|
||||
auto target = context.focus_manager().focus_widget();
|
||||
while( (target != nullptr) && !target->on_key(event) ) {
|
||||
target = target->parent();
|
||||
}
|
||||
|
||||
/* Return true if event was consumed. */
|
||||
return (target != nullptr);
|
||||
}
|
||||
|
||||
void EventDispatcher::event_bubble_encoder(const ui::EncoderEvent event) {
|
||||
auto target = context.focus_manager().focus_widget();
|
||||
while( (target != nullptr) && !target->on_encoder(event) ) {
|
||||
target = target->parent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __EVENT_M0_H__
|
||||
#define __EVENT_M0_H__
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include "touch.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr auto EVT_MASK_RTC_TICK = EVENT_MASK(0);
|
||||
constexpr auto EVT_MASK_LCD_FRAME_SYNC = EVENT_MASK(1);
|
||||
constexpr auto EVT_MASK_SWITCHES = EVENT_MASK(3);
|
||||
constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4);
|
||||
constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5);
|
||||
constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6);
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher(
|
||||
ui::Widget* const top_widget,
|
||||
ui::Painter& painter,
|
||||
ui::Context& context
|
||||
);
|
||||
|
||||
void run();
|
||||
void request_stop();
|
||||
|
||||
void set_display_sleep(const bool sleep);
|
||||
|
||||
static inline void events_flag(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignal(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void events_flag_isr(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignalI(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
|
||||
static MessageHandlerMap& message_map() {
|
||||
return message_map_;
|
||||
}
|
||||
|
||||
private:
|
||||
static MessageHandlerMap message_map_;
|
||||
static Thread* thread_event_loop;
|
||||
|
||||
touch::Manager touch_manager;
|
||||
ui::Widget* const top_widget;
|
||||
ui::Painter& painter;
|
||||
ui::Context& context;
|
||||
uint32_t encoder_last = 0;
|
||||
bool is_running = true;
|
||||
bool sd_card_present = false;
|
||||
bool display_sleep = false;
|
||||
|
||||
eventmask_t wait();
|
||||
void dispatch(const eventmask_t events);
|
||||
|
||||
void handle_application_queue();
|
||||
void handle_rtc_tick();
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
|
||||
|
||||
ui::Widget* captured_widget { nullptr };
|
||||
|
||||
void on_touch_event(ui::TouchEvent event);
|
||||
|
||||
void handle_lcd_frame_sync();
|
||||
void handle_switches();
|
||||
void handle_encoder();
|
||||
void handle_touch();
|
||||
|
||||
bool event_bubble_key(const ui::KeyEvent event);
|
||||
void event_bubble_encoder(const ui::EncoderEvent event);
|
||||
};
|
||||
|
||||
#endif/*__EVENT_M0_H__*/
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
File::~File() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool File::open_for_append(const std::string file_path) {
|
||||
const auto open_result = f_open(&f, file_path.c_str(), FA_WRITE | FA_OPEN_ALWAYS);
|
||||
if( open_result == FR_OK ) {
|
||||
const auto seek_result = f_lseek(&f, f_size(&f));
|
||||
if( seek_result == FR_OK ) {
|
||||
return true;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::close() {
|
||||
f_close(&f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::is_ready() {
|
||||
return f_error(&f) == 0;
|
||||
}
|
||||
|
||||
bool File::read(void* const data, const size_t bytes_to_read) {
|
||||
UINT bytes_read = 0;
|
||||
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
|
||||
return (result == FR_OK) && (bytes_read == bytes_to_read);
|
||||
}
|
||||
|
||||
bool File::write(const void* const data, const size_t bytes_to_write) {
|
||||
UINT bytes_written = 0;
|
||||
const auto result = f_write(&f, data, bytes_to_write, &bytes_written);
|
||||
return (result == FR_OK) && (bytes_written == bytes_to_write);
|
||||
}
|
||||
|
||||
bool File::puts(const std::string string) {
|
||||
const auto result = f_puts(string.c_str(), &f);
|
||||
return (result >= 0);
|
||||
}
|
||||
|
||||
bool File::sync() {
|
||||
const auto result = f_sync(&f);
|
||||
return (result == FR_OK);
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include "event.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "touch.hpp"
|
||||
#include "touch_adc.hpp"
|
||||
@@ -165,7 +165,7 @@ void timer0_callback(GPTDriver* const) {
|
||||
/* Signal event loop */
|
||||
if( event_mask ) {
|
||||
chSysLockFromIsr();
|
||||
events_flag_isr(event_mask);
|
||||
EventDispatcher::events_flag_isr(event_mask);
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "irq_lcd_frame.hpp"
|
||||
|
||||
#include "event.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -54,7 +54,7 @@ CH_IRQ_HANDLER(PIN_INT4_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
events_flag_isr(EVT_MASK_LCD_FRAME_SYNC);
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_LCD_FRAME_SYNC);
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
LPC_GPIO_INT->IST = (1U << 4);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "event.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
void rtc_interrupt_enable() {
|
||||
rtc::interrupt::enable_second_inc();
|
||||
@@ -39,7 +39,7 @@ CH_IRQ_HANDLER(RTC_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
events_flag_isr(EVT_MASK_RTC_TICK);
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_RTC_TICK);
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
rtc::interrupt::clear_all();
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
LogFile::LogFile(
|
||||
const std::string file_path
|
||||
) : file_path { file_path }
|
||||
{
|
||||
file.open_for_append(file_path);
|
||||
|
||||
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) {
|
||||
this->on_sd_card_status(status);
|
||||
};
|
||||
}
|
||||
|
||||
LogFile::~LogFile() {
|
||||
sd_card::status_signal -= sd_card_status_signal_token;
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool LogFile::is_ready() {
|
||||
return file.is_ready();
|
||||
}
|
||||
|
||||
bool LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
|
||||
std::string timestamp = to_string_timestamp(datetime);
|
||||
return write(timestamp + " " + entry + "\r\n");
|
||||
}
|
||||
|
||||
bool LogFile::write(const std::string& message) {
|
||||
return file.puts(message) && file.sync();
|
||||
}
|
||||
|
||||
void LogFile::on_sd_card_status(const sd_card::Status status) {
|
||||
if( status == sd_card::Status::Mounted ) {
|
||||
file.open_for_append(file_path);
|
||||
} else {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -19,32 +19,36 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __EVENT_H__
|
||||
#define __EVENT_H__
|
||||
#ifndef __LOG_FILE_H__
|
||||
#define __LOG_FILE_H__
|
||||
|
||||
#include "ch.h"
|
||||
#include <string>
|
||||
|
||||
constexpr auto EVT_MASK_RTC_TICK = EVENT_MASK(0);
|
||||
constexpr auto EVT_MASK_LCD_FRAME_SYNC = EVENT_MASK(1);
|
||||
constexpr auto EVT_MASK_SWITCHES = EVENT_MASK(3);
|
||||
constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4);
|
||||
constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5);
|
||||
constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6);
|
||||
#include "file.hpp"
|
||||
#include "sd_card.hpp"
|
||||
|
||||
void events_initialize(Thread* const event_loop_thread);
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
extern Thread* thread_event_loop;
|
||||
class LogFile {
|
||||
public:
|
||||
LogFile(const std::string file_path);
|
||||
~LogFile();
|
||||
|
||||
inline void events_flag(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignal(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
bool is_ready();
|
||||
|
||||
inline void events_flag_isr(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignalI(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
bool write_entry(const rtc::RTC& datetime, const std::string& entry);
|
||||
|
||||
#endif/*__EVENT_H__*/
|
||||
private:
|
||||
const std::string file_path;
|
||||
|
||||
File file;
|
||||
|
||||
SignalToken sd_card_status_signal_token;
|
||||
|
||||
bool write(const std::string& message);
|
||||
|
||||
void on_sd_card_status(const sd_card::Status status);
|
||||
};
|
||||
|
||||
#endif/*__LOG_FILE_H__*/
|
||||
@@ -49,16 +49,9 @@
|
||||
//TODO: TX power
|
||||
|
||||
#include "ch.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_io.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
@@ -74,7 +67,7 @@ using namespace portapack;
|
||||
#include "irq_controls.hpp"
|
||||
#include "irq_rtc.hpp"
|
||||
|
||||
#include "event.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "m4_startup.hpp"
|
||||
#include "spi_image.hpp"
|
||||
@@ -84,280 +77,13 @@ using namespace portapack;
|
||||
|
||||
#include "gcc.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher(
|
||||
ui::Widget* const top_widget,
|
||||
ui::Painter& painter,
|
||||
ui::Context& context
|
||||
) : top_widget { top_widget },
|
||||
painter(painter),
|
||||
context(context)
|
||||
{
|
||||
// touch_manager.on_started = [this](const ui::TouchEvent event) {
|
||||
// this->context.focus_manager.update(this->top_widget, event);
|
||||
// };
|
||||
|
||||
touch_manager.on_event = [this](const ui::TouchEvent event) {
|
||||
this->on_touch_event(event);
|
||||
};
|
||||
}
|
||||
|
||||
void run() {
|
||||
while(is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
}
|
||||
}
|
||||
|
||||
void request_stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
private:
|
||||
touch::Manager touch_manager;
|
||||
ui::Widget* const top_widget;
|
||||
ui::Painter& painter;
|
||||
ui::Context& context;
|
||||
uint32_t encoder_last = 0;
|
||||
bool is_running = true;
|
||||
bool sd_card_present = false;
|
||||
|
||||
eventmask_t wait() {
|
||||
return chEvtWaitAny(ALL_EVENTS);
|
||||
}
|
||||
|
||||
void dispatch(const eventmask_t events) {
|
||||
if( events & EVT_MASK_APPLICATION ) {
|
||||
handle_application_queue();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_RTC_TICK ) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_LCD_FRAME_SYNC ) {
|
||||
handle_lcd_frame_sync();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_SWITCHES ) {
|
||||
handle_switches();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_ENCODER ) {
|
||||
handle_encoder();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_TOUCH ) {
|
||||
handle_touch();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_application_queue() {
|
||||
std::array<uint8_t, Message::MAX_SIZE> message_buffer;
|
||||
while(Message* const message = shared_memory.application_queue.pop(message_buffer)) {
|
||||
context.message_map().send(message);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_rtc_tick() {
|
||||
uint16_t bloff_time;
|
||||
const auto sd_card_present_now = sdc_lld_is_card_inserted(&SDCD1);
|
||||
|
||||
bloff_time = portapack::persistent_memory::ui_config_bloff();
|
||||
if (bloff_time) {
|
||||
if (portapack::bl_tick_counter >= bloff_time)
|
||||
io.lcd_backlight(0);
|
||||
else
|
||||
portapack::bl_tick_counter++;
|
||||
}
|
||||
|
||||
if( sd_card_present_now != sd_card_present ) {
|
||||
sd_card_present = sd_card_present_now;
|
||||
|
||||
if( sd_card_present ) {
|
||||
if( sdcConnect(&SDCD1) == CH_SUCCESS ) {
|
||||
if( sd_card::filesystem::mount() == FR_OK ) {
|
||||
SDCardStatusMessage message { true };
|
||||
context.message_map().send(&message);
|
||||
} else {
|
||||
// TODO: Error, modal warning?
|
||||
}
|
||||
} else {
|
||||
// TODO: Error, modal warning?
|
||||
}
|
||||
} else {
|
||||
sdcDisconnect(&SDCD1);
|
||||
|
||||
SDCardStatusMessage message { false };
|
||||
context.message_map().send(&message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) {
|
||||
if( !w->hidden() ) {
|
||||
// To achieve reverse depth ordering (last object drawn is
|
||||
// considered "top"), descend first.
|
||||
for(const auto child : w->children()) {
|
||||
const auto touched_widget = touch_widget(child, event);
|
||||
if( touched_widget ) {
|
||||
return touched_widget;
|
||||
}
|
||||
}
|
||||
|
||||
const auto r = w->screen_rect();
|
||||
if( r.contains(event.point) ) {
|
||||
if( w->on_touch(event) ) {
|
||||
// This widget responded. Return it up the call stack.
|
||||
return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ui::Widget* captured_widget { nullptr };
|
||||
|
||||
void on_touch_event(ui::TouchEvent event) {
|
||||
/* TODO: Capture widget receiving the Start event, send Move and
|
||||
* End events to the same widget.
|
||||
*/
|
||||
/* Capture Start widget.
|
||||
* If touch is over Start widget at Move event, then the widget
|
||||
* should be highlighted. If the touch is not over the Start
|
||||
* widget at Move event, widget should un-highlight.
|
||||
* If touch is over Start widget at End event, then the widget
|
||||
* action should occur.
|
||||
*/
|
||||
if( event.type == ui::TouchEvent::Type::Start ) {
|
||||
captured_widget = touch_widget(this->top_widget, event);
|
||||
}
|
||||
|
||||
if( captured_widget ) {
|
||||
captured_widget->on_touch(event);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_lcd_frame_sync() {
|
||||
DisplayFrameSyncMessage message;
|
||||
context.message_map().send(&message);
|
||||
painter.paint_widget_tree(top_widget);
|
||||
}
|
||||
|
||||
void handle_switches() {
|
||||
const auto switches_state = get_switches_state();
|
||||
|
||||
io.lcd_backlight(1);
|
||||
portapack::bl_tick_counter = 0;
|
||||
|
||||
for(size_t i=0; i<switches_state.size(); i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
if( switches_state[i] ) {
|
||||
const auto event = static_cast<ui::KeyEvent>(i);
|
||||
if( !event_bubble_key(event) ) {
|
||||
context.focus_manager().update(top_widget, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_encoder() {
|
||||
const uint32_t encoder_now = get_encoder_position();
|
||||
const int32_t delta = static_cast<int32_t>(encoder_now - encoder_last);
|
||||
|
||||
io.lcd_backlight(1);
|
||||
portapack::bl_tick_counter = 0;
|
||||
|
||||
encoder_last = encoder_now;
|
||||
const auto event = static_cast<ui::EncoderEvent>(delta);
|
||||
event_bubble_encoder(event);
|
||||
}
|
||||
|
||||
void handle_touch() {
|
||||
touch_manager.feed(get_touch_frame());
|
||||
}
|
||||
|
||||
bool event_bubble_key(const ui::KeyEvent event) {
|
||||
auto target = context.focus_manager().focus_widget();
|
||||
while( (target != nullptr) && !target->on_key(event) ) {
|
||||
target = target->parent();
|
||||
}
|
||||
|
||||
/* Return true if event was consumed. */
|
||||
return (target != nullptr);
|
||||
}
|
||||
|
||||
void event_bubble_encoder(const ui::EncoderEvent event) {
|
||||
auto target = context.focus_manager().focus_widget();
|
||||
while( (target != nullptr) && !target->on_encoder(event) ) {
|
||||
target = target->parent();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Thinking things through a bit:
|
||||
|
||||
main() produces UI events.
|
||||
Touch events:
|
||||
Hit test entire screen hierarchy and send to hit widget.
|
||||
If modal view shown, block UI events destined outside.
|
||||
Navigation events:
|
||||
Move from current focus widget to "nearest" focusable widget.
|
||||
If current view is modal, don't allow events to bubble outside
|
||||
of modal view.
|
||||
System events:
|
||||
Power off from WWDT provides enough time to flush changes to
|
||||
VBAT RAM?
|
||||
SD card events? Insert/eject/error.
|
||||
|
||||
|
||||
View stack:
|
||||
Views that are hidden should deconstruct their widgets?
|
||||
Views that are shown after being hidden will reconstruct their
|
||||
widgets from data in their model?
|
||||
Hence, hidden views will not eat up memory beyond their model?
|
||||
Beware loops where the stack can get wildly deep?
|
||||
Breaking out data models from views should allow some amount of
|
||||
power-off persistence in the VBAT RAM area. In fact, the data
|
||||
models could be instantiated there? But then, how to protect
|
||||
from corruption if power is pulled? Does WWDT provide enough
|
||||
warning to flush changes?
|
||||
|
||||
Navigation...
|
||||
If you move off the left side of the screen, move to breadcrumb
|
||||
"back" item, no matter where you're coming from?
|
||||
*/
|
||||
|
||||
/*
|
||||
message_handlers[Message::ID::FSKPacket] = [](const Message* const p) {
|
||||
const auto message = static_cast<const FSKPacketMessage*>(p);
|
||||
fsk_packet(message);
|
||||
};
|
||||
|
||||
message_handlers[Message::ID::TestResults] = [&system_view](const Message* const p) {
|
||||
const auto message = static_cast<const TestResultsMessage*>(p);
|
||||
char c[10];
|
||||
c[0] = message->results.translate_by_fs_over_4_and_decimate_by_2_cic3 ? '+' : '-';
|
||||
c[1] = message->results.fir_cic3_decim_2_s16_s16 ? '+' : '-';
|
||||
c[2] = message->results.fir_64_and_decimate_by_2_complex ? '+' : '-';
|
||||
c[3] = message->results.fxpt_atan2 ? '+' : '-';
|
||||
c[4] = message->results.multiply_conjugate_s16_s32 ? '+' : '-';
|
||||
c[5] = 0;
|
||||
system_view.status_view.portapack.set(c);
|
||||
};
|
||||
*/
|
||||
|
||||
int main(void) {
|
||||
portapack::init();
|
||||
|
||||
@@ -365,14 +91,11 @@ int main(void) {
|
||||
chSysHalt();
|
||||
}
|
||||
|
||||
init_message_queues();
|
||||
|
||||
portapack::io.init();
|
||||
portapack::display.init();
|
||||
|
||||
sdcStart(&SDCD1, nullptr);
|
||||
|
||||
events_initialize(chThdSelf());
|
||||
init_message_queues();
|
||||
|
||||
ui::Context context;
|
||||
@@ -383,19 +106,22 @@ int main(void) {
|
||||
ui::Painter painter;
|
||||
EventDispatcher event_dispatcher { &system_view, painter, context };
|
||||
|
||||
auto& message_handlers = context.message_map();
|
||||
message_handlers.register_handler(Message::ID::Shutdown,
|
||||
EventDispatcher::message_map().register_handler(Message::ID::Shutdown,
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.request_stop();
|
||||
}
|
||||
);
|
||||
EventDispatcher::message_map().register_handler(Message::ID::DisplaySleep,
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}
|
||||
);
|
||||
|
||||
m4_init(portapack::spi_flash::baseband, portapack::memory::map::m4_code);
|
||||
|
||||
controls_init();
|
||||
lcd_frame_sync_configure();
|
||||
rtc_interrupt_enable();
|
||||
m4txevent_interrupt_enable();
|
||||
|
||||
event_dispatcher.run();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ constexpr std::array<uint8_t, 8> lookup_8db_steps {
|
||||
};
|
||||
|
||||
static uint_fast8_t gain_ordinal(const int8_t db) {
|
||||
int8_t db_sat = std::min(std::max(gain_db_min, db), gain_db_max);
|
||||
const auto db_sat = gain_db_range.clip(db);
|
||||
return lna::lookup_8db_steps[(db_sat >> 3) & 7];
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ static uint_fast8_t gain_ordinal(const int8_t db) {
|
||||
namespace vga {
|
||||
|
||||
static uint_fast8_t gain_ordinal(const int8_t db) {
|
||||
int8_t db_sat = std::min(std::max(gain_db_min, db), gain_db_max);
|
||||
const auto db_sat = gain_db_range.clip(db);
|
||||
return ((db_sat >> 1) & 0b11111) ^ 0b11111;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@ void MAX2837::init() {
|
||||
|
||||
_dirty.set();
|
||||
flush();
|
||||
|
||||
set_mode(Mode::Standby);
|
||||
}
|
||||
|
||||
void MAX2837::set_mode(const Mode mode) {
|
||||
@@ -149,22 +151,22 @@ void MAX2837::flush_one(const Register reg) {
|
||||
_dirty.clear(reg_num);
|
||||
}
|
||||
|
||||
inline void MAX2837::write(const address_t reg_num, const reg_t value) {
|
||||
void MAX2837::write(const address_t reg_num, const reg_t value) {
|
||||
uint16_t t = (0U << 15) | (reg_num << 10) | (value & 0x3ffU);
|
||||
_target.transfer(&t, 1);
|
||||
}
|
||||
|
||||
inline reg_t MAX2837::read(const address_t reg_num) {
|
||||
reg_t MAX2837::read(const address_t reg_num) {
|
||||
uint16_t t = (1U << 15) | (reg_num << 10);
|
||||
_target.transfer(&t, 1U);
|
||||
return t & 0x3ffU;
|
||||
}
|
||||
|
||||
inline void MAX2837::write(const Register reg, const reg_t value) {
|
||||
void MAX2837::write(const Register reg, const reg_t value) {
|
||||
write(toUType(reg), value);
|
||||
}
|
||||
|
||||
inline reg_t MAX2837::read(const Register reg) {
|
||||
reg_t MAX2837::read(const Register reg) {
|
||||
return read(toUType(reg));
|
||||
}
|
||||
|
||||
@@ -194,18 +196,16 @@ void MAX2837::set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) {
|
||||
|
||||
bool MAX2837::set_frequency(const rf::Frequency lo_frequency) {
|
||||
/* TODO: This is a sad implementation. Refactor. */
|
||||
if( lo_frequency < lo::band[0].min ) {
|
||||
return false;
|
||||
} else if( lo_frequency < lo::band[0].max ) {
|
||||
if( lo::band[0].contains(lo_frequency) ) {
|
||||
_map.r.syn_int_div.LOGEN_BSW = 0b00; /* 2300 - 2399.99MHz */
|
||||
_map.r.rxrf_1.LNAband = 0; /* 2.3 - 2.5GHz */
|
||||
} else if( lo_frequency < lo::band[1].max ) {
|
||||
} else if( lo::band[1].contains(lo_frequency) ) {
|
||||
_map.r.syn_int_div.LOGEN_BSW = 0b01; /* 2400 - 2499.99MHz */
|
||||
_map.r.rxrf_1.LNAband = 0; /* 2.3 - 2.5GHz */
|
||||
} else if( lo_frequency < lo::band[2].max ) {
|
||||
} else if( lo::band[2].contains(lo_frequency) ) {
|
||||
_map.r.syn_int_div.LOGEN_BSW = 0b10; /* 2500 - 2599.99MHz */
|
||||
_map.r.rxrf_1.LNAband = 1; /* 2.5 - 2.7GHz */
|
||||
} else if( lo_frequency < lo::band[3].max ) {
|
||||
} else if( lo::band[3].contains(lo_frequency) ) {
|
||||
_map.r.syn_int_div.LOGEN_BSW = 0b11; /* 2600 - 2700Hz */
|
||||
_map.r.rxrf_1.LNAband = 1; /* 2.5 - 2.7GHz */
|
||||
} else {
|
||||
|
||||
@@ -50,10 +50,10 @@ enum class Mode {
|
||||
namespace lo {
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 4> band { {
|
||||
{ .min = 2300000000, .max = 2400000000, },
|
||||
{ .min = 2400000000, .max = 2500000000, },
|
||||
{ .min = 2500000000, .max = 2600000000, },
|
||||
{ .min = 2600000000, .max = 2700000000, },
|
||||
{ 2300000000, 2400000000 },
|
||||
{ 2400000000, 2500000000 },
|
||||
{ 2500000000, 2600000000 },
|
||||
{ 2600000000, 2700000000 },
|
||||
} };
|
||||
|
||||
} /* namespace lo */
|
||||
@@ -62,13 +62,12 @@ constexpr std::array<rf::FrequencyRange, 4> band { {
|
||||
|
||||
namespace lna {
|
||||
|
||||
constexpr int8_t gain_db_min = 0;
|
||||
constexpr int8_t gain_db_max = 40;
|
||||
constexpr range_t<int8_t> gain_db_range { 0, 40 };
|
||||
constexpr int8_t gain_db_step = 8;
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 2> band { {
|
||||
{ .min = 2300000000, .max = 2500000000, },
|
||||
{ .min = 2500000000, .max = 2700000000, },
|
||||
{ 2300000000, 2500000000 },
|
||||
{ 2500000000, 2700000000 },
|
||||
} };
|
||||
|
||||
} /* namespace lna */
|
||||
@@ -77,8 +76,7 @@ constexpr std::array<rf::FrequencyRange, 2> band { {
|
||||
|
||||
namespace vga {
|
||||
|
||||
constexpr int8_t gain_db_min = 0;
|
||||
constexpr int8_t gain_db_max = 62;
|
||||
constexpr range_t<int8_t> gain_db_range { 0, 62 };
|
||||
constexpr int8_t gain_db_step = 2;
|
||||
|
||||
} /* namespace vga */
|
||||
@@ -576,7 +574,7 @@ constexpr RegisterMap initial_register_values { Register_Type {
|
||||
.LNAtune = 0,
|
||||
.LNAde_Q = 1,
|
||||
.L = 0b000,
|
||||
.iqerr_trim = 0b0000,
|
||||
.iqerr_trim = 0b00000,
|
||||
.RESERVED0 = 0,
|
||||
},
|
||||
.lpf_1 = { /* 2 */
|
||||
@@ -880,6 +878,8 @@ public:
|
||||
|
||||
reg_t temp_sense();
|
||||
|
||||
reg_t read(const address_t reg_num);
|
||||
|
||||
private:
|
||||
spi::arbiter::Target& _target;
|
||||
|
||||
@@ -889,7 +889,6 @@ private:
|
||||
void flush_one(const Register reg);
|
||||
|
||||
void write(const address_t reg_num, const reg_t value);
|
||||
reg_t read(const address_t reg_num);
|
||||
|
||||
void write(const Register reg, const reg_t value);
|
||||
reg_t read(const Register reg);
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "hackrf_gpio.hpp"
|
||||
using namespace hackrf::one;
|
||||
|
||||
#include "si5351.hpp"
|
||||
#include "clock_manager.hpp"
|
||||
|
||||
#include "i2c_pp.hpp"
|
||||
@@ -66,6 +65,8 @@ ReceiverModel receiver_model {
|
||||
clock_manager
|
||||
};
|
||||
|
||||
TemperatureLogger temperature_logger;
|
||||
|
||||
TransmitterModel transmitter_model {
|
||||
clock_manager
|
||||
};
|
||||
|
||||
@@ -26,9 +26,11 @@
|
||||
|
||||
#include "spi_pp.hpp"
|
||||
#include "wm8731.hpp"
|
||||
#include "si5351.hpp"
|
||||
#include "lcd_ili9341.hpp"
|
||||
|
||||
#include "radio.hpp"
|
||||
#include "temperature_logger.hpp"
|
||||
|
||||
namespace portapack {
|
||||
|
||||
@@ -41,11 +43,15 @@ extern SPI ssp1;
|
||||
|
||||
extern wolfson::wm8731::WM8731 audio_codec;
|
||||
|
||||
extern si5351::Si5351 clock_generator;
|
||||
|
||||
extern ReceiverModel receiver_model;
|
||||
extern TransmitterModel transmitter_model;
|
||||
|
||||
extern uint8_t bl_tick_counter;
|
||||
|
||||
extern TemperatureLogger temperature_logger;
|
||||
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
#include "radio.hpp"
|
||||
|
||||
#include "rf_path.hpp"
|
||||
#include "max2837.hpp"
|
||||
#include "max5864.hpp"
|
||||
#include "baseband_cpld.hpp"
|
||||
#include "baseband_sgpio.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "tuning.hpp"
|
||||
@@ -88,10 +86,9 @@ static spi::arbiter::Target ssp1_target_max5864 {
|
||||
|
||||
static rf::path::Path rf_path;
|
||||
rffc507x::RFFC507x first_if;
|
||||
static max2837::MAX2837 second_if { ssp1_target_max2837 };
|
||||
max2837::MAX2837 second_if { ssp1_target_max2837 };
|
||||
static max5864::MAX5864 baseband_codec { ssp1_target_max5864 };
|
||||
static baseband::CPLD baseband_cpld;
|
||||
static baseband::SGPIO baseband_sgpio;
|
||||
|
||||
static rf::Direction direction { rf::Direction::Receive };
|
||||
|
||||
@@ -101,7 +98,6 @@ void init() {
|
||||
second_if.init();
|
||||
baseband_codec.init();
|
||||
baseband_cpld.init();
|
||||
baseband_sgpio.init();
|
||||
}
|
||||
|
||||
void set_direction(const rf::Direction new_direction) {
|
||||
@@ -113,7 +109,6 @@ void set_direction(const rf::Direction new_direction) {
|
||||
rf_path.set_direction(direction);
|
||||
|
||||
baseband_codec.set_mode((direction == rf::Direction::Transmit) ? max5864::Mode::Transmit : max5864::Mode::Receive);
|
||||
baseband_sgpio.configure((direction == rf::Direction::Transmit) ? baseband::Direction::Transmit : baseband::Direction::Receive);
|
||||
}
|
||||
|
||||
bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
@@ -157,18 +152,15 @@ void set_baseband_decimation_by(const size_t n) {
|
||||
baseband_cpld.set_decimation_by(n);
|
||||
}
|
||||
|
||||
void streaming_enable() {
|
||||
baseband_sgpio.streaming_enable();
|
||||
}
|
||||
|
||||
void streaming_disable() {
|
||||
baseband_sgpio.streaming_disable();
|
||||
void set_antenna_bias(const bool on) {
|
||||
/* Pull MOSFET gate low to turn on antenna bias. */
|
||||
first_if.set_gpo1(on ? 0 : 1);
|
||||
}
|
||||
|
||||
void disable() {
|
||||
baseband_sgpio.streaming_disable();
|
||||
set_antenna_bias(false);
|
||||
baseband_codec.set_mode(max5864::Mode::Shutdown);
|
||||
second_if.set_mode(max2837::Mode::Shutdown);
|
||||
second_if.set_mode(max2837::Mode::Standby);
|
||||
first_if.disable();
|
||||
set_rf_amp(false);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <cstddef>
|
||||
|
||||
#include "rffc507x.hpp"
|
||||
#include "max2837.hpp"
|
||||
|
||||
namespace radio {
|
||||
|
||||
@@ -41,12 +42,12 @@ void set_vga_gain(const int_fast8_t db);
|
||||
void set_sampling_frequency(const uint32_t frequency);
|
||||
void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum);
|
||||
void set_baseband_decimation_by(const size_t n);
|
||||
void set_antenna_bias(const bool on);
|
||||
|
||||
void streaming_enable();
|
||||
void streaming_disable();
|
||||
void disable();
|
||||
|
||||
extern rffc507x::RFFC507x first_if;
|
||||
extern max2837::MAX2837 second_if;
|
||||
|
||||
} /* namespace radio */
|
||||
|
||||
|
||||
@@ -52,6 +52,15 @@ void ReceiverModel::set_reference_ppm_correction(int32_t v) {
|
||||
clock_manager.set_reference_ppb(v * 1000);
|
||||
}
|
||||
|
||||
bool ReceiverModel::antenna_bias() const {
|
||||
return antenna_bias_;
|
||||
}
|
||||
|
||||
void ReceiverModel::set_antenna_bias(bool enabled) {
|
||||
antenna_bias_ = enabled;
|
||||
update_antenna_bias();
|
||||
}
|
||||
|
||||
bool ReceiverModel::rf_amp() const {
|
||||
return rf_amp_;
|
||||
}
|
||||
@@ -92,12 +101,10 @@ uint32_t ReceiverModel::sampling_rate() const {
|
||||
return baseband_configuration.sampling_rate;
|
||||
}
|
||||
|
||||
|
||||
uint32_t ReceiverModel::modulation() const {
|
||||
return baseband_configuration.mode;
|
||||
}
|
||||
|
||||
|
||||
volume_t ReceiverModel::headphone_volume() const {
|
||||
return headphone_volume_;
|
||||
}
|
||||
@@ -112,36 +119,41 @@ uint32_t ReceiverModel::baseband_oversampling() const {
|
||||
return baseband_configuration.decimation_factor;
|
||||
}
|
||||
|
||||
|
||||
void ReceiverModel::enable() {
|
||||
enabled_ = true;
|
||||
radio::set_direction(rf::Direction::Receive);
|
||||
update_tuning_frequency();
|
||||
update_antenna_bias();
|
||||
update_rf_amp();
|
||||
update_lna();
|
||||
update_vga();
|
||||
update_baseband_bandwidth();
|
||||
update_baseband_configuration();
|
||||
radio::streaming_enable();
|
||||
|
||||
update_headphone_volume();
|
||||
}
|
||||
|
||||
void ReceiverModel::disable() {
|
||||
/* TODO: This is a dumb hack to stop baseband from working so hard. */
|
||||
BasebandConfigurationMessage message {
|
||||
.configuration = {
|
||||
.mode = NONE,
|
||||
.sampling_rate = 0,
|
||||
.decimation_factor = 1,
|
||||
void ReceiverModel::baseband_disable() {
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
BasebandConfigurationMessage {
|
||||
.configuration = { },
|
||||
}
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
);
|
||||
}
|
||||
|
||||
void ReceiverModel::disable() {
|
||||
enabled_ = false;
|
||||
update_antenna_bias();
|
||||
baseband_disable();
|
||||
|
||||
// TODO: Responsibility for enabling/disabling the radio is muddy.
|
||||
// Some happens in ReceiverModel, some inside radio namespace.
|
||||
radio::disable();
|
||||
}
|
||||
|
||||
int32_t ReceiverModel::tuning_offset() {
|
||||
if( baseband_configuration.mode == 4 ) {
|
||||
if( (baseband_configuration.mode == 4) ||
|
||||
(baseband_configuration.mode == 6) ) {
|
||||
return 0;
|
||||
} else {
|
||||
return -(sampling_rate() / 4);
|
||||
@@ -152,6 +164,10 @@ void ReceiverModel::update_tuning_frequency() {
|
||||
radio::set_tuning_frequency(persistent_memory::tuned_frequency() + tuning_offset());
|
||||
}
|
||||
|
||||
void ReceiverModel::update_antenna_bias() {
|
||||
radio::set_antenna_bias(antenna_bias_ && enabled_);
|
||||
}
|
||||
|
||||
void ReceiverModel::update_rf_amp() {
|
||||
radio::set_rf_amp(rf_amp_);
|
||||
}
|
||||
@@ -174,7 +190,12 @@ void ReceiverModel::set_baseband_configuration(const BasebandConfiguration confi
|
||||
}
|
||||
|
||||
void ReceiverModel::update_baseband_configuration() {
|
||||
radio::streaming_disable();
|
||||
// TODO: Move more low-level radio control stuff to M4. It'll enable tighter
|
||||
// synchronization for things like wideband (sweeping) spectrum analysis, and
|
||||
// protocols that need quick RX/TX turn-around.
|
||||
|
||||
// Disabling baseband while changing sampling rates seems like a good idea...
|
||||
baseband_disable();
|
||||
|
||||
clock_manager.set_sampling_frequency(sampling_rate() * baseband_oversampling());
|
||||
update_tuning_frequency();
|
||||
@@ -182,8 +203,6 @@ void ReceiverModel::update_baseband_configuration() {
|
||||
|
||||
BasebandConfigurationMessage message { baseband_configuration };
|
||||
shared_memory.baseband_queue.push(message);
|
||||
|
||||
radio::streaming_enable();
|
||||
}
|
||||
|
||||
void ReceiverModel::update_headphone_volume() {
|
||||
|
||||
@@ -33,6 +33,16 @@
|
||||
|
||||
class ReceiverModel {
|
||||
public:
|
||||
enum class Mode : int32_t {
|
||||
AMAudio = 0,
|
||||
NarrowbandFMAudio = 1,
|
||||
WidebandFMAudio = 2,
|
||||
AIS = 3,
|
||||
SpectrumAnalysis = 4,
|
||||
TPMS = 5,
|
||||
ERT = 6,
|
||||
};
|
||||
|
||||
constexpr ReceiverModel(
|
||||
ClockManager& clock_manager
|
||||
) : clock_manager(clock_manager)
|
||||
@@ -48,6 +58,9 @@ public:
|
||||
int32_t reference_ppm_correction() const;
|
||||
void set_reference_ppm_correction(int32_t v);
|
||||
|
||||
bool antenna_bias() const;
|
||||
void set_antenna_bias(bool enabled);
|
||||
|
||||
bool rf_amp() const;
|
||||
void set_rf_amp(bool enabled);
|
||||
|
||||
@@ -76,14 +89,16 @@ public:
|
||||
|
||||
private:
|
||||
rf::Frequency frequency_step_ { 25000 };
|
||||
bool enabled_ { false };
|
||||
bool rf_amp_ { false };
|
||||
bool antenna_bias_ { false };
|
||||
int32_t lna_gain_db_ { 32 };
|
||||
uint32_t baseband_bandwidth_ { max2837::filter::bandwidth_minimum };
|
||||
int32_t vga_gain_db_ { 32 };
|
||||
BasebandConfiguration baseband_configuration {
|
||||
.mode = NONE,
|
||||
.mode = 1, /* TODO: Enum! */
|
||||
.sampling_rate = 3072000,
|
||||
.decimation_factor = 4,
|
||||
.decimation_factor = 1,
|
||||
};
|
||||
volume_t headphone_volume_ { -43.0_dB };
|
||||
ClockManager& clock_manager;
|
||||
@@ -91,6 +106,7 @@ private:
|
||||
int32_t tuning_offset();
|
||||
|
||||
void update_tuning_frequency();
|
||||
void update_antenna_bias();
|
||||
void update_rf_amp();
|
||||
void update_lna();
|
||||
void update_baseband_bandwidth();
|
||||
@@ -98,6 +114,7 @@ private:
|
||||
void update_baseband_configuration();
|
||||
void update_headphone_volume();
|
||||
|
||||
void baseband_disable();
|
||||
};
|
||||
|
||||
#endif/*__RECEIVER_MODEL_H__*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -19,9 +19,4 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __IRQ_IPC_H__
|
||||
#define __IRQ_IPC_H__
|
||||
|
||||
void m4txevent_interrupt_enable();
|
||||
|
||||
#endif/*__IRQ_IPC_H__*/
|
||||
#include "recent_entries.hpp"
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __RECENT_ENTRIES_H__
|
||||
#define __RECENT_ENTRIES_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
template<class Packet, class Entry>
|
||||
class RecentEntries {
|
||||
public:
|
||||
using EntryType = Entry;
|
||||
using Key = typename Entry::Key;
|
||||
using ContainerType = std::list<Entry>;
|
||||
using const_reference = typename ContainerType::const_reference;
|
||||
using const_iterator = typename ContainerType::const_iterator;
|
||||
using RangeType = std::pair<const_iterator, const_iterator>;
|
||||
|
||||
const Entry& on_packet(const Key key, const Packet& packet) {
|
||||
auto matching_recent = find(key);
|
||||
if( matching_recent != std::end(entries) ) {
|
||||
// Found within. Move to front of list, increment counter.
|
||||
entries.push_front(*matching_recent);
|
||||
entries.erase(matching_recent);
|
||||
} else {
|
||||
entries.emplace_front(key);
|
||||
truncate_entries();
|
||||
}
|
||||
|
||||
auto& entry = entries.front();
|
||||
entry.update(packet);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
const_reference front() const {
|
||||
return entries.front();
|
||||
}
|
||||
|
||||
const_iterator find(const Key key) const {
|
||||
return std::find_if(
|
||||
std::begin(entries), std::end(entries),
|
||||
[key](const Entry& e) { return e.key() == key; }
|
||||
);
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return entries.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return entries.end();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return entries.empty();
|
||||
}
|
||||
|
||||
RangeType range_around(
|
||||
const_iterator item, const size_t count
|
||||
) const {
|
||||
auto start = item;
|
||||
auto end = item;
|
||||
size_t i = 0;
|
||||
|
||||
// Move start iterator toward first entry.
|
||||
while( (start != std::begin(entries)) && (i < count / 2) ) {
|
||||
std::advance(start, -1);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Move end iterator toward last entry.
|
||||
while( (end != std::end(entries)) && (i < count) ) {
|
||||
std::advance(end, 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
private:
|
||||
ContainerType entries;
|
||||
const size_t entries_max = 64;
|
||||
|
||||
void truncate_entries() {
|
||||
while(entries.size() > entries_max) {
|
||||
entries.pop_back();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
template<class Entries>
|
||||
class RecentEntriesView : public View {
|
||||
public:
|
||||
using Entry = typename Entries::EntryType;
|
||||
|
||||
std::function<void(const Entry& entry)> on_select;
|
||||
|
||||
RecentEntriesView(
|
||||
Entries& recent
|
||||
) : recent { recent }
|
||||
{
|
||||
flags.focusable = true;
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override {
|
||||
const auto r = screen_rect();
|
||||
const auto& s = style();
|
||||
|
||||
Rect target_rect { r.pos, { r.width(), s.font.line_height() }};
|
||||
const size_t visible_item_count = r.height() / s.font.line_height();
|
||||
|
||||
const Style style_header {
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::blue(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
draw_header(target_rect, painter, style_header);
|
||||
target_rect.pos.y += target_rect.height();
|
||||
|
||||
auto selected = recent.find(selected_key);
|
||||
if( selected == std::end(recent) ) {
|
||||
selected = std::begin(recent);
|
||||
}
|
||||
|
||||
auto range = recent.range_around(selected, visible_item_count);
|
||||
|
||||
for(auto p = range.first; p != range.second; p++) {
|
||||
const auto& entry = *p;
|
||||
const auto is_selected_key = (selected_key == entry.key());
|
||||
draw(entry, target_rect, painter, s, (has_focus() && is_selected_key));
|
||||
target_rect.pos.y += target_rect.height();
|
||||
}
|
||||
|
||||
painter.fill_rectangle(
|
||||
{ target_rect.left(), target_rect.top(), target_rect.width(), r.bottom() - target_rect.top() },
|
||||
style().background
|
||||
);
|
||||
}
|
||||
|
||||
bool on_encoder(const EncoderEvent event) override {
|
||||
advance(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool on_key(const ui::KeyEvent event) override {
|
||||
if( event == ui::KeyEvent::Select ) {
|
||||
if( on_select ) {
|
||||
const auto selected = recent.find(selected_key);
|
||||
if( selected != std::end(recent) ) {
|
||||
on_select(*selected);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void on_focus() override {
|
||||
advance(0);
|
||||
}
|
||||
|
||||
private:
|
||||
Entries& recent;
|
||||
|
||||
using EntryKey = typename Entry::Key;
|
||||
EntryKey selected_key = Entry::invalid_key;
|
||||
|
||||
void advance(const int32_t amount) {
|
||||
auto selected = recent.find(selected_key);
|
||||
if( selected == std::end(recent) ) {
|
||||
if( recent.empty() ) {
|
||||
selected_key = Entry::invalid_key;
|
||||
} else {
|
||||
selected_key = recent.front().key();
|
||||
}
|
||||
} else {
|
||||
if( amount < 0 ) {
|
||||
if( selected != std::begin(recent) ) {
|
||||
std::advance(selected, -1);
|
||||
}
|
||||
}
|
||||
if( amount > 0 ) {
|
||||
std::advance(selected, 1);
|
||||
if( selected == std::end(recent) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selected_key = selected->key();
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void draw_header(
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style
|
||||
);
|
||||
|
||||
void draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style,
|
||||
const bool is_selected
|
||||
);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__RECENT_ENTRIES_H__*/
|
||||
@@ -22,29 +22,14 @@
|
||||
#ifndef __RF_PATH_H__
|
||||
#define __RF_PATH_H__
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace rf {
|
||||
|
||||
using Frequency = int64_t;
|
||||
|
||||
struct FrequencyRange {
|
||||
Frequency min;
|
||||
Frequency max;
|
||||
/* TODO: static_assert low < high? */
|
||||
|
||||
bool below_range(const Frequency f) const {
|
||||
return f < min;
|
||||
}
|
||||
|
||||
bool contains(const Frequency f) const {
|
||||
return (f >= min) && (f < max);
|
||||
}
|
||||
|
||||
bool out_of_range(const Frequency f) const {
|
||||
return !contains(f);
|
||||
}
|
||||
};
|
||||
using FrequencyRange = range_t<Frequency>;
|
||||
|
||||
enum class Direction {
|
||||
/* Zero-based, used as index into table */
|
||||
@@ -54,20 +39,9 @@ enum class Direction {
|
||||
|
||||
namespace path {
|
||||
|
||||
constexpr FrequencyRange band_low {
|
||||
.min = 0,
|
||||
.max = 2150000000,
|
||||
};
|
||||
|
||||
constexpr FrequencyRange band_high {
|
||||
.min = 2750000000,
|
||||
.max = 7250000000,
|
||||
};
|
||||
|
||||
constexpr FrequencyRange band_mid {
|
||||
.min = band_low.max,
|
||||
.max = band_high.min,
|
||||
};
|
||||
constexpr FrequencyRange band_low { 0, 2150000000 };
|
||||
constexpr FrequencyRange band_high { 2750000000, 7250000000 };
|
||||
constexpr FrequencyRange band_mid { band_low.maximum, band_high.minimum };
|
||||
|
||||
enum class Band {
|
||||
/* Zero-based, used as index into frequency_bands table */
|
||||
@@ -94,10 +68,7 @@ private:
|
||||
|
||||
} /* path */
|
||||
|
||||
constexpr FrequencyRange tuning_range {
|
||||
.min = path::band_low.min,
|
||||
.max = path::band_high.max,
|
||||
};
|
||||
constexpr FrequencyRange tuning_range { path::band_low.minimum, path::band_high.maximum };
|
||||
|
||||
} /* rf */
|
||||
|
||||
|
||||
@@ -51,10 +51,7 @@ constexpr auto reference_frequency = rffc5072_reference_f;
|
||||
|
||||
namespace vco {
|
||||
|
||||
constexpr rf::FrequencyRange range {
|
||||
.min = 2700000000U,
|
||||
.max = 5400000000U,
|
||||
};
|
||||
constexpr rf::FrequencyRange range { 2700000000, 5400000000 };
|
||||
|
||||
} /* namespace vco */
|
||||
|
||||
@@ -66,10 +63,7 @@ constexpr size_t divider_log2_max = 5;
|
||||
constexpr size_t divider_min = 1U << divider_log2_min;
|
||||
constexpr size_t divider_max = 1U << divider_log2_max;
|
||||
|
||||
constexpr rf::FrequencyRange range {
|
||||
.min = vco::range.min / divider_max,
|
||||
.max = vco::range.max / divider_min,
|
||||
};
|
||||
constexpr rf::FrequencyRange range { vco::range.minimum / divider_max, vco::range.maximum / divider_min };
|
||||
|
||||
size_t divider_log2(const rf::Frequency lo_frequency) {
|
||||
/* TODO: Error */
|
||||
@@ -183,23 +177,23 @@ void RFFC507x::flush() {
|
||||
}
|
||||
}
|
||||
|
||||
inline void RFFC507x::write(const address_t reg_num, const spi::reg_t value) {
|
||||
void RFFC507x::write(const address_t reg_num, const spi::reg_t value) {
|
||||
_bus.write(reg_num, value);
|
||||
}
|
||||
|
||||
inline spi::reg_t RFFC507x::read(const address_t reg_num) {
|
||||
spi::reg_t RFFC507x::read(const address_t reg_num) {
|
||||
return _bus.read(reg_num);
|
||||
}
|
||||
|
||||
inline void RFFC507x::write(const Register reg, const spi::reg_t value) {
|
||||
void RFFC507x::write(const Register reg, const spi::reg_t value) {
|
||||
write(toUType(reg), value);
|
||||
}
|
||||
|
||||
inline spi::reg_t RFFC507x::read(const Register reg) {
|
||||
spi::reg_t RFFC507x::read(const Register reg) {
|
||||
return read(toUType(reg));
|
||||
}
|
||||
|
||||
inline void RFFC507x::flush_one(const Register reg) {
|
||||
void RFFC507x::flush_one(const Register reg) {
|
||||
const auto reg_num = toUType(reg);
|
||||
write(reg_num, _map.w[reg_num]);
|
||||
_dirty.clear(reg_num);
|
||||
@@ -262,6 +256,18 @@ void RFFC507x::set_frequency(const rf::Frequency lo_frequency) {
|
||||
flush();
|
||||
}
|
||||
|
||||
void RFFC507x::set_gpo1(const bool new_value) {
|
||||
if( new_value ) {
|
||||
_map.r.gpo.p2gpo |= 1;
|
||||
_map.r.gpo.p1gpo |= 1;
|
||||
} else {
|
||||
_map.r.gpo.p2gpo &= ~1;
|
||||
_map.r.gpo.p1gpo &= ~1;
|
||||
}
|
||||
|
||||
flush_one(Register::GPO);
|
||||
}
|
||||
|
||||
spi::reg_t RFFC507x::readback(const Readback readback) {
|
||||
/* TODO: This clobbers the rest of the DEV_CTRL register
|
||||
* Time to implement bitfields for registers.
|
||||
@@ -272,10 +278,6 @@ spi::reg_t RFFC507x::readback(const Readback readback) {
|
||||
return read(Register::READBACK);
|
||||
}
|
||||
|
||||
RegisterMap RFFC507x::registers() {
|
||||
return _map;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Test of RFFC507x reset over temperature */
|
||||
while(true) {
|
||||
|
||||
@@ -808,8 +808,9 @@ public:
|
||||
|
||||
void set_mixer_current(const uint8_t value);
|
||||
void set_frequency(const rf::Frequency lo_frequency);
|
||||
|
||||
RegisterMap registers();
|
||||
void set_gpo1(const bool new_value);
|
||||
|
||||
reg_t read(const address_t reg_num);
|
||||
|
||||
private:
|
||||
spi::SPI _bus;
|
||||
@@ -818,7 +819,6 @@ private:
|
||||
DirtyRegisters<Register, reg_count> _dirty;
|
||||
|
||||
void write(const address_t reg_num, const reg_t value);
|
||||
reg_t read(const address_t reg_num);
|
||||
|
||||
void write(const Register reg, const reg_t value);
|
||||
reg_t read(const Register reg);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include <hal.h>
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
namespace sd_card {
|
||||
|
||||
namespace {
|
||||
|
||||
bool card_present = false;
|
||||
|
||||
Status status_ { Status::NotPresent };
|
||||
|
||||
FATFS fs;
|
||||
|
||||
FRESULT mount() {
|
||||
return f_mount(&fs, "", 0);
|
||||
}
|
||||
|
||||
FRESULT unmount() {
|
||||
return f_mount(NULL, "", 0);
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
Signal<Status> status_signal;
|
||||
|
||||
void poll_inserted() {
|
||||
const auto card_present_now = sdc_lld_is_card_inserted(&SDCD1);
|
||||
if( card_present_now != card_present ) {
|
||||
card_present = card_present_now;
|
||||
|
||||
Status new_status { card_present ? Status::Present : Status::NotPresent };
|
||||
|
||||
if( card_present ) {
|
||||
if( sdcConnect(&SDCD1) == CH_SUCCESS ) {
|
||||
if( mount() == FR_OK ) {
|
||||
new_status = Status::Mounted;
|
||||
} else {
|
||||
new_status = Status::MountError;
|
||||
}
|
||||
} else {
|
||||
new_status = Status::ConnectError;
|
||||
}
|
||||
} else {
|
||||
sdcDisconnect(&SDCD1);
|
||||
}
|
||||
|
||||
status_ = new_status;
|
||||
status_signal.emit(status_);
|
||||
}
|
||||
}
|
||||
|
||||
Status status() {
|
||||
return status_;
|
||||
}
|
||||
|
||||
} /* namespace sd_card */
|
||||
@@ -22,26 +22,26 @@
|
||||
#ifndef __SD_CARD_H__
|
||||
#define __SD_CARD_H__
|
||||
|
||||
#include "ff.h"
|
||||
#include <cstdint>
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
namespace sd_card {
|
||||
namespace filesystem {
|
||||
|
||||
namespace {
|
||||
enum class Status : int32_t {
|
||||
IOError = -3,
|
||||
MountError = -2,
|
||||
ConnectError = -1,
|
||||
NotPresent = 0,
|
||||
Present = 1,
|
||||
Mounted = 2,
|
||||
};
|
||||
|
||||
FATFS fs;
|
||||
extern Signal<Status> status_signal;
|
||||
|
||||
}
|
||||
void poll_inserted();
|
||||
Status status();
|
||||
|
||||
FRESULT mount() {
|
||||
return f_mount(&fs, "", 0);
|
||||
}
|
||||
|
||||
FRESULT unmount() {
|
||||
return f_mount(NULL, "", 0);
|
||||
}
|
||||
|
||||
} /* namespace filesystem */
|
||||
} /* namespace sd_card */
|
||||
|
||||
#endif/*__SD_CARD_H__*/
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SIGNAL_H__
|
||||
#define __SIGNAL_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
/* Tweaked and vastly simplified implementation of Simple::Signal, from
|
||||
* https://testbit.eu/cpp11-signal-system-performance/
|
||||
*
|
||||
* Original license:
|
||||
* CC0 Public Domain
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
using SignalToken = uint32_t;
|
||||
|
||||
template<class... Args>
|
||||
struct Signal {
|
||||
using Callback = std::function<void (Args...)>;
|
||||
|
||||
SignalToken operator+=(const Callback& callback) {
|
||||
const SignalToken token = next_token++;
|
||||
entries.emplace_back(std::make_unique<CallbackEntry>(callback, token));
|
||||
return token;
|
||||
}
|
||||
|
||||
bool operator-=(const SignalToken token) {
|
||||
entries.remove_if([token](EntryType& entry) {
|
||||
return entry.get()->token == token;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void emit(Args... args) {
|
||||
for(auto& entry : entries) {
|
||||
entry.get()->callback(args...);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
struct CallbackEntry {
|
||||
const Callback callback;
|
||||
const SignalToken token;
|
||||
|
||||
constexpr CallbackEntry(
|
||||
const Callback& callback,
|
||||
const SignalToken token
|
||||
) : callback { callback },
|
||||
token { token }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using EntryType = std::unique_ptr<CallbackEntry>;
|
||||
|
||||
std::list<EntryType> entries;
|
||||
SignalToken next_token = 1;
|
||||
};
|
||||
|
||||
#endif/*__SIGNAL_H__*/
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -19,12 +19,16 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "event.hpp"
|
||||
#include "spectrum_analysis_app.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
Thread* thread_event_loop = nullptr;
|
||||
|
||||
void events_initialize(Thread* const event_loop_thread) {
|
||||
thread_event_loop = event_loop_thread;
|
||||
SpectrumAnalysisModel::SpectrumAnalysisModel() {
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = 4,
|
||||
.sampling_rate = 20000000,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(12000000);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SPECTRUM_ANALYSIS_APP_H__
|
||||
#define __SPECTRUM_ANALYSIS_APP_H__
|
||||
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
class SpectrumAnalysisModel {
|
||||
public:
|
||||
SpectrumAnalysisModel();
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
|
||||
public:
|
||||
|
||||
private:
|
||||
SpectrumAnalysisModel model;
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__SPECTRUM_ANALYSIS_APP_H__*/
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
static char* to_string_dec_uint_internal(
|
||||
char* p,
|
||||
uint32_t n
|
||||
) {
|
||||
*p = 0;
|
||||
auto q = p;
|
||||
|
||||
do {
|
||||
const uint32_t d = n % 10;
|
||||
const char c = d + 48;
|
||||
*(--q) = c;
|
||||
n /= 10;
|
||||
} while( n != 0 );
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static char* to_string_dec_uint_pad_internal(
|
||||
char* const term,
|
||||
const uint32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
auto q = to_string_dec_uint_internal(term, n);
|
||||
|
||||
if( fill ) {
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = fill;
|
||||
}
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
std::string to_string_dec_uint(
|
||||
const uint32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
char p[16];
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n, l, fill);
|
||||
|
||||
// Right justify.
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
std::string to_string_dec_int(
|
||||
const int32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
const size_t negative = (n < 0) ? 1 : 0;
|
||||
uint32_t n_abs = negative ? -n : n;
|
||||
|
||||
char p[16];
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);
|
||||
|
||||
// Add sign.
|
||||
if( negative ) {
|
||||
*(--q) = '-';
|
||||
}
|
||||
|
||||
// Right justify.
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static void to_string_hex_internal(char* p, const uint32_t n, const int32_t l) {
|
||||
const uint32_t d = n & 0xf;
|
||||
p[l] = (d > 9) ? (d + 87) : (d + 48);
|
||||
if( l > 0 ) {
|
||||
to_string_hex_internal(p, n >> 4, l - 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string to_string_hex(const uint32_t n, const int32_t l) {
|
||||
char p[16];
|
||||
to_string_hex_internal(p, n, l - 1);
|
||||
p[l] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string to_string_datetime(const rtc::RTC& value) {
|
||||
return to_string_dec_uint(value.year(), 4, '0') + "/" +
|
||||
to_string_dec_uint(value.month(), 2, '0') + "/" +
|
||||
to_string_dec_uint(value.day(), 2, '0') + " " +
|
||||
to_string_dec_uint(value.hour(), 2, '0') + ":" +
|
||||
to_string_dec_uint(value.minute(), 2, '0') + ":" +
|
||||
to_string_dec_uint(value.second(), 2, '0');
|
||||
}
|
||||
|
||||
std::string to_string_timestamp(const rtc::RTC& value) {
|
||||
return to_string_dec_uint(value.year(), 4, '0') +
|
||||
to_string_dec_uint(value.month(), 2, '0') +
|
||||
to_string_dec_uint(value.day(), 2, '0') +
|
||||
to_string_dec_uint(value.hour(), 2, '0') +
|
||||
to_string_dec_uint(value.minute(), 2, '0') +
|
||||
to_string_dec_uint(value.second(), 2, '0');
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __STRING_FORMAT_H__
|
||||
#define __STRING_FORMAT_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// BARF! rtc::RTC is leaking everywhere.
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
|
||||
|
||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0);
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
|
||||
std::string to_string_hex(const uint32_t n, const int32_t l = 0);
|
||||
|
||||
std::string to_string_datetime(const rtc::RTC& value);
|
||||
std::string to_string_timestamp(const rtc::RTC& value);
|
||||
|
||||
#endif/*__STRING_FORMAT_H__*/
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "temperature_logger.hpp"
|
||||
|
||||
#include "radio.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void TemperatureLogger::second_tick() {
|
||||
sample_phase++;
|
||||
if( sample_phase >= sample_interval ) {
|
||||
push_sample(read_sample());
|
||||
}
|
||||
}
|
||||
|
||||
size_t TemperatureLogger::size() const {
|
||||
return std::min(capacity(), samples_count);
|
||||
}
|
||||
|
||||
size_t TemperatureLogger::capacity() const {
|
||||
return samples.size();
|
||||
}
|
||||
|
||||
std::vector<TemperatureLogger::sample_t> TemperatureLogger::history() const {
|
||||
std::vector<sample_t> result;
|
||||
|
||||
const auto n = size();
|
||||
result.resize(n);
|
||||
|
||||
// Copy the last N samples from the buffer, since new samples are added at the end.
|
||||
std::copy(samples.cend() - n, samples.cend(), result.data());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TemperatureLogger::sample_t TemperatureLogger::read_sample() {
|
||||
// MAX2837 does not return a valid temperature if in "shutdown" mode.
|
||||
return radio::second_if.temp_sense() & 0x1f;
|
||||
}
|
||||
|
||||
void TemperatureLogger::push_sample(const TemperatureLogger::sample_t sample) {
|
||||
// Started out building a pseudo-FIFO, then got lazy.
|
||||
|
||||
// Shift samples: samples[1:] -> samples[0:-1]
|
||||
// New sample goes into samples[-1]
|
||||
std::copy(samples.cbegin() + 1, samples.cend(), samples.begin());
|
||||
samples.back() = sample;
|
||||
samples_count++;
|
||||
sample_phase = 0;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __TEMPERATURE_LOGGER_H__
|
||||
#define __TEMPERATURE_LOGGER_H__
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class TemperatureLogger {
|
||||
public:
|
||||
using sample_t = uint8_t;
|
||||
|
||||
void second_tick();
|
||||
|
||||
size_t size() const;
|
||||
size_t capacity() const;
|
||||
|
||||
std::vector<sample_t> history() const;
|
||||
|
||||
private:
|
||||
std::array<sample_t, 128> samples;
|
||||
|
||||
static constexpr size_t sample_interval = 5;
|
||||
size_t sample_phase = 0;
|
||||
size_t samples_count = 0;
|
||||
|
||||
sample_t read_sample();
|
||||
void push_sample(const sample_t sample);
|
||||
};
|
||||
|
||||
#endif/*__TEMPERATURE_LOGGER_H__*/
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "tpms_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "crc.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace tpms {
|
||||
|
||||
namespace format {
|
||||
|
||||
std::string type(Reading::Type type) {
|
||||
return to_string_dec_uint(toUType(type), 2);
|
||||
}
|
||||
|
||||
std::string id(TransponderID id) {
|
||||
return to_string_hex(id.value(), 8);
|
||||
}
|
||||
|
||||
std::string pressure(Pressure pressure) {
|
||||
return to_string_dec_int(pressure.kilopascal(), 3);
|
||||
}
|
||||
|
||||
std::string temperature(Temperature temperature) {
|
||||
return to_string_dec_int(temperature.celsius(), 3);
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
Timestamp Packet::received_at() const {
|
||||
return packet_.timestamp();
|
||||
}
|
||||
|
||||
ManchesterFormatted Packet::symbols_formatted() const {
|
||||
return format_manchester(decoder_);
|
||||
}
|
||||
|
||||
Optional<Reading> Packet::reading() const {
|
||||
const auto length = crc_valid_length();
|
||||
|
||||
switch(length) {
|
||||
case 64:
|
||||
return Reading {
|
||||
Reading::Type::FLM_64,
|
||||
reader_.read(0, 32),
|
||||
Pressure { static_cast<int>(reader_.read(32, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(reader_.read(40, 8) & 0x7f) - 50 }
|
||||
};
|
||||
|
||||
case 72:
|
||||
return Reading {
|
||||
Reading::Type::FLM_72,
|
||||
reader_.read(0, 32),
|
||||
Pressure { static_cast<int>(reader_.read(40, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(reader_.read(48, 8)) - 50 }
|
||||
};
|
||||
|
||||
case 80:
|
||||
return Reading {
|
||||
Reading::Type::FLM_80,
|
||||
reader_.read(8, 32),
|
||||
Pressure { static_cast<int>(reader_.read(48, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(reader_.read(56, 8)) - 50 }
|
||||
};
|
||||
|
||||
default:
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
size_t Packet::crc_valid_length() const {
|
||||
constexpr uint32_t checksum_bytes = 0b1111111;
|
||||
constexpr uint32_t crc_72_bytes = 0b111111111;
|
||||
constexpr uint32_t crc_80_bytes = 0b1111111110;
|
||||
|
||||
std::array<uint8_t, 10> bytes;
|
||||
for(size_t i=0; i<bytes.size(); i++) {
|
||||
bytes[i] = reader_.read(i * 8, 8);
|
||||
}
|
||||
|
||||
uint32_t checksum = 0;
|
||||
CRC<uint8_t> crc_72 { 0x01, 0x00 };
|
||||
CRC<uint8_t> crc_80 { 0x01, 0x00 };
|
||||
|
||||
for(size_t i=0; i<bytes.size(); i++) {
|
||||
const uint32_t byte_mask = 1 << i;
|
||||
const auto byte = bytes[i];
|
||||
|
||||
if( checksum_bytes & byte_mask ) {
|
||||
checksum += byte;
|
||||
}
|
||||
if( crc_72_bytes & byte_mask ) {
|
||||
crc_72.process_byte(byte);
|
||||
}
|
||||
if( crc_80_bytes & byte_mask ) {
|
||||
crc_80.process_byte(byte);
|
||||
}
|
||||
}
|
||||
|
||||
if( crc_80.checksum() == 0 ) {
|
||||
return 80;
|
||||
} else if( crc_72.checksum() == 0 ) {
|
||||
return 72;
|
||||
} else if( (checksum & 0xff) == bytes[7] ) {
|
||||
return 64;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
void TPMSLogger::on_packet(const tpms::Packet& packet) {
|
||||
const auto hex_formatted = packet.symbols_formatted();
|
||||
|
||||
if( log_file.is_ready() ) {
|
||||
const auto tuning_frequency = receiver_model.tuning_frequency();
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10);
|
||||
|
||||
std::string entry = tuning_frequency_str + " FSK 38.4 19.2 " + hex_formatted.data + "/" + hex_formatted.errors;
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
const TPMSRecentEntry::Key TPMSRecentEntry::invalid_key = { tpms::Reading::Type::None, 0 };
|
||||
|
||||
void TPMSRecentEntry::update(const tpms::Reading& reading) {
|
||||
received_count++;
|
||||
|
||||
if( reading.pressure().is_valid() ) {
|
||||
last_pressure = reading.pressure();
|
||||
}
|
||||
if( reading.temperature().is_valid() ) {
|
||||
last_temperature = reading.temperature();
|
||||
}
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const std::array<std::pair<std::string, size_t>, 5> tpms_columns { {
|
||||
{ "Tp", 2 },
|
||||
{ "ID", 8 },
|
||||
{ "kPa", 3 },
|
||||
{ "C", 3 },
|
||||
{ "Cnt", 3 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<TPMSRecentEntries>::draw_header(
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style
|
||||
) {
|
||||
auto x = 0;
|
||||
for(const auto& column : tpms_columns) {
|
||||
const auto width = column.second;
|
||||
auto text = column.first;
|
||||
if( width > text.length() ) {
|
||||
text.append(width - text.length(), ' ');
|
||||
}
|
||||
|
||||
painter.draw_string({ x, target_rect.pos.y }, style, text);
|
||||
x += (width * 8) + 8;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<TPMSRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style,
|
||||
const bool is_selected
|
||||
) {
|
||||
const auto& draw_style = is_selected ? style.invert() : style;
|
||||
|
||||
std::string line = tpms::format::type(entry.type) + " " + tpms::format::id(entry.id);
|
||||
|
||||
if( entry.last_pressure.is_valid() ) {
|
||||
line += " " + tpms::format::pressure(entry.last_pressure.value());
|
||||
} else {
|
||||
line += " " " ";
|
||||
}
|
||||
|
||||
if( entry.last_temperature.is_valid() ) {
|
||||
line += " " + tpms::format::temperature(entry.last_temperature.value());
|
||||
} else {
|
||||
line += " " " ";
|
||||
}
|
||||
|
||||
if( entry.received_count > 999 ) {
|
||||
line += " +++";
|
||||
} else {
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.pos, draw_style, line);
|
||||
}
|
||||
|
||||
TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
add_children({ {
|
||||
&recent_entries_view,
|
||||
} });
|
||||
|
||||
EventDispatcher::message_map().register_handler(Message::ID::TPMSPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const TPMSPacketMessage*>(p);
|
||||
const tpms::Packet packet { message->packet };
|
||||
this->on_packet(packet);
|
||||
}
|
||||
);
|
||||
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = 5,
|
||||
.sampling_rate = 2457600,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_rf_amp(false);
|
||||
receiver_model.set_lna(32);
|
||||
receiver_model.set_vga(32);
|
||||
receiver_model.set_tuning_frequency(315000000);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
TPMSAppView::~TPMSAppView() {
|
||||
receiver_model.disable();
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::TPMSPacket);
|
||||
}
|
||||
|
||||
void TPMSAppView::focus() {
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
recent_entries_view.set_parent_rect({ 0, 0, new_parent_rect.width(), new_parent_rect.height() });
|
||||
}
|
||||
|
||||
void TPMSAppView::on_packet(const tpms::Packet& packet) {
|
||||
logger.on_packet(packet);
|
||||
|
||||
const auto reading_opt = packet.reading();
|
||||
if( reading_opt.is_valid() ) {
|
||||
const auto reading = reading_opt.value();
|
||||
recent.on_packet({ reading.type(), reading.id() }, reading);
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TPMSAppView::on_show_list() {
|
||||
recent_entries_view.hidden(false);
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __TPMS_APP_H__
|
||||
#define __TPMS_APP_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "field_reader.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
#include "manchester.hpp"
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
#include "optional.hpp"
|
||||
|
||||
#include "units.hpp"
|
||||
using units::Temperature;
|
||||
using units::Pressure;
|
||||
|
||||
namespace tpms {
|
||||
|
||||
class TransponderID {
|
||||
public:
|
||||
constexpr TransponderID(
|
||||
) : id_ { 0 }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr TransponderID(
|
||||
const uint32_t id
|
||||
) : id_ { id }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr uint32_t value() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t id_;
|
||||
};
|
||||
|
||||
class Reading {
|
||||
public:
|
||||
enum Type {
|
||||
None = 0,
|
||||
FLM_64 = 1,
|
||||
FLM_72 = 2,
|
||||
FLM_80 = 3,
|
||||
};
|
||||
|
||||
constexpr Reading(
|
||||
) : type_ { Type::None }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Reading(
|
||||
Type type,
|
||||
TransponderID id
|
||||
) : type_ { type },
|
||||
id_ { id }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Reading(
|
||||
Type type,
|
||||
TransponderID id,
|
||||
Optional<Pressure> pressure = { },
|
||||
Optional<Temperature> temperature = { }
|
||||
) : type_ { type },
|
||||
id_ { id },
|
||||
pressure_ { pressure },
|
||||
temperature_ { temperature }
|
||||
{
|
||||
}
|
||||
|
||||
Type type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
TransponderID id() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
Optional<Pressure> pressure() const {
|
||||
return pressure_;
|
||||
}
|
||||
|
||||
Optional<Temperature> temperature() const {
|
||||
return temperature_;
|
||||
}
|
||||
|
||||
private:
|
||||
Type type_ { Type::None };
|
||||
TransponderID id_ { 0 };
|
||||
Optional<Pressure> pressure_ { };
|
||||
Optional<Temperature> temperature_ { };
|
||||
};
|
||||
|
||||
class Packet {
|
||||
public:
|
||||
constexpr Packet(
|
||||
const baseband::Packet& packet
|
||||
) : packet_ { packet },
|
||||
decoder_ { packet_, 0 },
|
||||
reader_ { decoder_ }
|
||||
{
|
||||
}
|
||||
|
||||
Timestamp received_at() const;
|
||||
|
||||
ManchesterFormatted symbols_formatted() const;
|
||||
|
||||
Optional<Reading> reading() const;
|
||||
|
||||
private:
|
||||
using Reader = FieldReader<ManchesterDecoder, BitRemapNone>;
|
||||
|
||||
const baseband::Packet packet_;
|
||||
const ManchesterDecoder decoder_;
|
||||
|
||||
const Reader reader_;
|
||||
|
||||
size_t crc_valid_length() const;
|
||||
};
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
namespace std {
|
||||
|
||||
constexpr bool operator==(const tpms::TransponderID& lhs, const tpms::TransponderID& rhs) {
|
||||
return (lhs.value() == rhs.value());
|
||||
}
|
||||
|
||||
} /* namespace std */
|
||||
|
||||
struct TPMSRecentEntry {
|
||||
using Key = std::pair<tpms::Reading::Type, tpms::TransponderID>;
|
||||
|
||||
static const Key invalid_key;
|
||||
|
||||
tpms::Reading::Type type { invalid_key.first };
|
||||
tpms::TransponderID id { invalid_key.second };
|
||||
|
||||
size_t received_count { 0 };
|
||||
|
||||
Optional<Pressure> last_pressure;
|
||||
Optional<Temperature> last_temperature;
|
||||
|
||||
TPMSRecentEntry(
|
||||
const Key& key
|
||||
) : type { key.first },
|
||||
id { key.second }
|
||||
{
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return { type, id };
|
||||
}
|
||||
|
||||
void update(const tpms::Reading& reading);
|
||||
};
|
||||
|
||||
using TPMSRecentEntries = RecentEntries<tpms::Reading, TPMSRecentEntry>;
|
||||
|
||||
class TPMSLogger {
|
||||
public:
|
||||
void on_packet(const tpms::Packet& packet);
|
||||
|
||||
private:
|
||||
LogFile log_file { "tpms.txt" };
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
using TPMSRecentEntriesView = RecentEntriesView<TPMSRecentEntries>;
|
||||
|
||||
class TPMSAppView : public View {
|
||||
public:
|
||||
TPMSAppView(NavigationView& nav);
|
||||
~TPMSAppView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
// Prevent painting of region covered entirely by a child.
|
||||
// TODO: Add flag to View that specifies view does not need to be cleared before painting.
|
||||
void paint(Painter&) override { };
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "TPMS"; };
|
||||
|
||||
private:
|
||||
TPMSRecentEntries recent;
|
||||
TPMSLogger logger;
|
||||
|
||||
TPMSRecentEntriesView recent_entries_view { recent };
|
||||
|
||||
void on_packet(const tpms::Packet& packet);
|
||||
void on_show_list();
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__TPMS_APP_H__*/
|
||||
@@ -19,8 +19,6 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "tuning.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
@@ -51,45 +49,30 @@ constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target
|
||||
Config low_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency + low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = first_lo_frequency - target_frequency;
|
||||
return {
|
||||
.first_lo_frequency = first_lo_frequency,
|
||||
.second_lo_frequency = second_lo_frequency,
|
||||
.rf_path_band = rf::path::Band::Low,
|
||||
.baseband_q_invert = true,
|
||||
};
|
||||
const bool baseband_q_invert = true;
|
||||
return { first_lo_frequency, second_lo_frequency, rf::path::Band::Low, baseband_q_invert };
|
||||
}
|
||||
|
||||
Config mid_band(const rf::Frequency target_frequency) {
|
||||
return {
|
||||
.first_lo_frequency = 0,
|
||||
.second_lo_frequency = target_frequency,
|
||||
.rf_path_band = rf::path::Band::Mid,
|
||||
.baseband_q_invert = false,
|
||||
};
|
||||
return { 0, target_frequency, rf::path::Band::Mid, false };
|
||||
}
|
||||
|
||||
Config high_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency - high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = target_frequency - first_lo_frequency;
|
||||
return {
|
||||
.first_lo_frequency = first_lo_frequency,
|
||||
.second_lo_frequency = second_lo_frequency,
|
||||
.rf_path_band = rf::path::Band::High,
|
||||
.baseband_q_invert = false,
|
||||
};
|
||||
const bool baseband_q_invert = false;
|
||||
return { first_lo_frequency, second_lo_frequency, rf::path::Band::High, baseband_q_invert };
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
Config create(const rf::Frequency target_frequency) {
|
||||
/* TODO: This is some lame code. */
|
||||
if( target_frequency < rf::path::band_low.min ) {
|
||||
return { };
|
||||
} else if( target_frequency < rf::path::band_low.max ) {
|
||||
if( rf::path::band_low.contains(target_frequency) ) {
|
||||
return low_band(target_frequency);
|
||||
} else if( target_frequency < rf::path::band_mid.max ) {
|
||||
} else if( rf::path::band_mid.contains(target_frequency) ) {
|
||||
return mid_band(target_frequency);
|
||||
} else if( target_frequency < rf::path::band_high.max ) {
|
||||
} else if( rf::path::band_high.contains(target_frequency) ) {
|
||||
return high_band(target_frequency);
|
||||
} else {
|
||||
return { };
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_audio.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void Audio::on_show() {
|
||||
context().message_map().register_handler(Message::ID::AudioStatistics,
|
||||
EventDispatcher::message_map().register_handler(Message::ID::AudioStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const AudioStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
@@ -34,21 +38,23 @@ void Audio::on_show() {
|
||||
}
|
||||
|
||||
void Audio::on_hide() {
|
||||
context().message_map().unregister_handler(Message::ID::AudioStatistics);
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::AudioStatistics);
|
||||
}
|
||||
|
||||
void Audio::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
const int32_t db_min = -r.width();
|
||||
const int32_t x_0 = 0;
|
||||
const int32_t x_rms = std::max(x_0, rms_db_ - db_min);
|
||||
const int32_t x_max = std::max(x_rms + 1, max_db_ - db_min);
|
||||
const int32_t x_lim = r.width();
|
||||
constexpr int db_min = -96;
|
||||
constexpr int db_max = 0;
|
||||
constexpr int db_delta = db_max - db_min;
|
||||
const range_t<int> x_rms_range { 0, r.width() - 1 };
|
||||
const auto x_rms = x_rms_range.clip((rms_db_ - db_min) * r.width() / db_delta);
|
||||
const range_t<int> x_max_range { x_rms + 1, r.width() };
|
||||
const auto x_max = x_max_range.clip((max_db_ - db_min) * r.width() / db_delta);
|
||||
|
||||
const Rect r0 {
|
||||
static_cast<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_rms - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_rms), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@@ -75,7 +81,7 @@ void Audio::paint(Painter& painter) {
|
||||
|
||||
const Rect r3 {
|
||||
static_cast<ui::Coord>(r.left() + x_max), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - x_max), r.height()
|
||||
static_cast<ui::Dim>(r.width() - x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r3,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_baseband_stats_view.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "hackrf_hal.hpp"
|
||||
using namespace hackrf::one;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* BasebandStatsView *****************************************************/
|
||||
@@ -38,7 +42,7 @@ BasebandStatsView::BasebandStatsView() {
|
||||
}
|
||||
|
||||
void BasebandStatsView::on_show() {
|
||||
context().message_map().register_handler(Message::ID::BasebandStatistics,
|
||||
EventDispatcher::message_map().register_handler(Message::ID::BasebandStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const BasebandStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
@@ -46,7 +50,7 @@ void BasebandStatsView::on_show() {
|
||||
}
|
||||
|
||||
void BasebandStatsView::on_hide() {
|
||||
context().message_map().unregister_handler(Message::ID::BasebandStatistics);
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::BasebandStatistics);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_channel.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void Channel::on_show() {
|
||||
context().message_map().register_handler(Message::ID::ChannelStatistics,
|
||||
EventDispatcher::message_map().register_handler(Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
@@ -34,20 +38,21 @@ void Channel::on_show() {
|
||||
}
|
||||
|
||||
void Channel::on_hide() {
|
||||
context().message_map().unregister_handler(Message::ID::ChannelStatistics);
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::ChannelStatistics);
|
||||
}
|
||||
|
||||
void Channel::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
const int32_t db_min = -r.width();
|
||||
const int32_t x_0 = 0;
|
||||
const int32_t x_max = std::max(x_0, max_db_ - db_min);
|
||||
const int32_t x_lim = r.width();
|
||||
constexpr int db_min = -96;
|
||||
constexpr int db_max = 0;
|
||||
constexpr int db_delta = db_max - db_min;
|
||||
const range_t<int> x_max_range { 0, r.width() - 1 };
|
||||
const auto x_max = x_max_range.clip((max_db_ - db_min) * r.width() / db_delta);
|
||||
|
||||
const Rect r0 {
|
||||
static_cast<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_max - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@@ -65,7 +70,7 @@ void Channel::paint(Painter& painter) {
|
||||
|
||||
const Rect r2 {
|
||||
static_cast<ui::Coord>(r.left() + x_max + 1), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - (x_max + 1)), r.height()
|
||||
static_cast<ui::Dim>(r.width() - (x_max + 1)), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r2,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -40,17 +40,21 @@ void Console::write(const std::string message) {
|
||||
const Font& font = s.font;
|
||||
const auto rect = screen_rect();
|
||||
for(const auto c : message) {
|
||||
const auto glyph = font.glyph(c);
|
||||
const auto advance = glyph.advance();
|
||||
if( (pos.x + advance.x) > rect.width() ) {
|
||||
if( c == '\n' ) {
|
||||
crlf();
|
||||
} else {
|
||||
const auto glyph = font.glyph(c);
|
||||
const auto advance = glyph.advance();
|
||||
if( (pos.x + advance.x) > rect.width() ) {
|
||||
crlf();
|
||||
}
|
||||
const Point pos_glyph {
|
||||
rect.pos.x + pos.x,
|
||||
display.scroll_area_y(pos.y)
|
||||
};
|
||||
display.draw_glyph(pos_glyph, glyph, s.foreground, s.background);
|
||||
pos.x += advance.x;
|
||||
}
|
||||
const Point pos_glyph {
|
||||
static_cast<Coord>(rect.pos.x + pos.x),
|
||||
display.scroll_area_y(pos.y)
|
||||
};
|
||||
display.draw_glyph(pos_glyph, glyph, s.foreground, s.background);
|
||||
pos.x += advance.x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+168
-123
@@ -24,16 +24,16 @@
|
||||
#include "ch.h"
|
||||
|
||||
#include "ff.h"
|
||||
#include "led.hpp"
|
||||
#include "hackrf_gpio.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "radio.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
FRESULT fr; /* FatFs function common result code */
|
||||
|
||||
/* DebugMemoryView *******************************************************/
|
||||
|
||||
DebugMemoryView::DebugMemoryView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
@@ -62,156 +62,186 @@ void DebugMemoryView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
void DebugRFFC5072RegistersWidget::update() {
|
||||
/* TemperatureWidget *****************************************************/
|
||||
|
||||
void TemperatureWidget::paint(Painter& painter) {
|
||||
const auto logger = portapack::temperature_logger;
|
||||
|
||||
const auto rect = screen_rect();
|
||||
const Color color_background { 0, 0, 64 };
|
||||
const Color color_foreground = Color::green();
|
||||
const Color color_reticle { 128, 128, 128 };
|
||||
|
||||
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
||||
const Rect graph_rect {
|
||||
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
|
||||
graph_width, rect.height()
|
||||
};
|
||||
const Rect frame_rect {
|
||||
graph_rect.left() - 1, graph_rect.top() - 1,
|
||||
graph_rect.width() + 2, graph_rect.height() + 2
|
||||
};
|
||||
painter.draw_rectangle(frame_rect, color_reticle);
|
||||
painter.fill_rectangle(graph_rect, color_background);
|
||||
|
||||
const auto history = logger.history();
|
||||
for(size_t i=0; i<history.size(); i++) {
|
||||
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
||||
const auto sample = history[i];
|
||||
const auto temp = temperature(sample);
|
||||
const auto y = screen_y(temp, graph_rect);
|
||||
const Dim bar_height = graph_rect.bottom() - y;
|
||||
painter.fill_rectangle({ x, y, bar_width, bar_height }, color_foreground);
|
||||
}
|
||||
|
||||
if( !history.empty() ) {
|
||||
const auto sample = history.back();
|
||||
const auto temp = temperature(sample);
|
||||
const auto last_y = screen_y(temp, graph_rect);
|
||||
const Coord x = graph_rect.right() + 8;
|
||||
const Coord y = last_y - 8;
|
||||
|
||||
painter.draw_string({ x, y }, style(), temperature_str(temp));
|
||||
}
|
||||
|
||||
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
|
||||
for(auto temp=display_temp_min; temp<=display_temp_max; temp+=10) {
|
||||
const int32_t tick_length = 6;
|
||||
const auto tick_x = graph_rect.left() - tick_length;
|
||||
const auto tick_y = screen_y(temp, graph_rect);
|
||||
painter.fill_rectangle({ tick_x, tick_y, tick_length, 1 }, color_reticle);
|
||||
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
|
||||
const auto text_y = tick_y - 8;
|
||||
painter.draw_string({ text_x, text_y }, style(), temperature_str(temp));
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
|
||||
return -45 + sensor_value * 5;
|
||||
}
|
||||
|
||||
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
|
||||
return to_string_dec_int(temperature, temp_len - 1) + "C";
|
||||
}
|
||||
|
||||
Coord TemperatureWidget::screen_y(
|
||||
const temperature_t temperature,
|
||||
const Rect& rect
|
||||
) const {
|
||||
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
|
||||
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
|
||||
return y_limit;
|
||||
}
|
||||
|
||||
/* TemperatureView *******************************************************/
|
||||
|
||||
TemperatureView::TemperatureView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&temperature_widget,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_done.on_select = [&nav](Button&){ nav.pop(); };
|
||||
}
|
||||
|
||||
void TemperatureView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* RegistersWidget *******************************************************/
|
||||
|
||||
RegistersWidget::RegistersWidget(
|
||||
RegistersWidgetConfig&& config,
|
||||
std::function<uint32_t(const size_t register_number)>&& reader
|
||||
) : Widget { },
|
||||
config(std::move(config)),
|
||||
reader(std::move(reader))
|
||||
{
|
||||
}
|
||||
|
||||
void RegistersWidget::update() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void DebugRFFC5072RegistersWidget::paint(Painter& painter) {
|
||||
draw_legend(painter);
|
||||
void RegistersWidget::paint(Painter& painter) {
|
||||
const Coord left = (size().w - config.row_width()) / 2;
|
||||
|
||||
const auto registers = radio::first_if.registers();
|
||||
draw_values(painter, registers);
|
||||
draw_legend(left, painter);
|
||||
draw_values(left, painter);
|
||||
}
|
||||
|
||||
void DebugRFFC5072RegistersWidget::draw_legend(Painter& painter) {
|
||||
for(size_t i=0; i<registers_count; i+=registers_per_row) {
|
||||
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
for(int i=0; i<config.registers_count; i+=config.registers_per_row) {
|
||||
const Point offset {
|
||||
0, static_cast<Coord>((i / registers_per_row) * row_height)
|
||||
left, (i / config.registers_per_row) * row_height
|
||||
};
|
||||
|
||||
const auto text = to_string_hex(i, legend_length);
|
||||
const auto text = to_string_hex(i, config.legend_length);
|
||||
painter.draw_string(
|
||||
screen_pos() + offset,
|
||||
style(),
|
||||
pos + offset,
|
||||
style().invert(),
|
||||
text
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugRFFC5072RegistersWidget::draw_values(
|
||||
Painter& painter,
|
||||
const rffc507x::RegisterMap registers
|
||||
void RegistersWidget::draw_values(
|
||||
const Coord left,
|
||||
Painter& painter
|
||||
) {
|
||||
for(size_t i=0; i<registers_count; i++) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
for(int i=0; i<config.registers_count; i++) {
|
||||
const Point offset = {
|
||||
static_cast<Coord>(legend_width + 8 + (i % registers_per_row) * (value_width + 8)),
|
||||
static_cast<Coord>((i / registers_per_row) * row_height)
|
||||
left + config.legend_width() + 8 + (i % config.registers_per_row) * (config.value_width() + 8),
|
||||
(i / config.registers_per_row) * row_height
|
||||
};
|
||||
|
||||
const uint16_t value = registers.w[i];
|
||||
const auto value = reader(i);
|
||||
|
||||
const auto text = to_string_hex(value, value_length);
|
||||
const auto text = to_string_hex(value, config.value_length);
|
||||
painter.draw_string(
|
||||
screen_pos() + offset,
|
||||
pos + offset,
|
||||
style(),
|
||||
text
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DebugRFFC5072View::DebugRFFC5072View(NavigationView& nav) {
|
||||
/* RegistersView *********************************************************/
|
||||
|
||||
RegistersView::RegistersView(
|
||||
NavigationView& nav,
|
||||
const std::string& title,
|
||||
RegistersWidgetConfig&& config,
|
||||
std::function<uint32_t(const size_t register_number)>&& reader
|
||||
) : registers_widget { std::move(config), std::move(reader) }
|
||||
{
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&widget_registers,
|
||||
®isters_widget,
|
||||
&button_update,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_update.on_select = [this](Button&){
|
||||
this->widget_registers.update();
|
||||
this->registers_widget.update();
|
||||
};
|
||||
button_done.on_select = [&nav](Button&){ nav.pop(); };
|
||||
|
||||
registers_widget.set_parent_rect({ 0, 48, 240, 192 });
|
||||
|
||||
text_title.set_parent_rect({
|
||||
(240 - static_cast<int>(title.size()) * 8) / 2, 16,
|
||||
static_cast<int>(title.size()) * 8, 16
|
||||
});
|
||||
text_title.set(title);
|
||||
}
|
||||
|
||||
void DebugRFFC5072View::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
void DebugSDView::paint(Painter& painter) {
|
||||
const Point offset = {
|
||||
static_cast<Coord>(32),
|
||||
static_cast<Coord>(32)
|
||||
};
|
||||
|
||||
const auto text = to_string_hex(fr, 2);
|
||||
painter.draw_string(
|
||||
screen_pos() + offset,
|
||||
style(),
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
DebugSDView::DebugSDView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&text_modules,
|
||||
&button_makefile,
|
||||
&button_done
|
||||
} });
|
||||
|
||||
FIL fdst;
|
||||
char buffer[256];
|
||||
uint8_t mods_version, mods_count;
|
||||
UINT bw;
|
||||
|
||||
const auto open_result = f_open(&fdst, "ppmods.bin", FA_OPEN_EXISTING | FA_READ);
|
||||
if (open_result == FR_OK) {
|
||||
f_read(&fdst, &mods_version, 1, &bw);
|
||||
if (mods_version == 1) {
|
||||
f_read(&fdst, &mods_count, 1, &bw);
|
||||
f_read(&fdst, buffer, 8, &bw);
|
||||
f_read(&fdst, buffer, 16, &bw);
|
||||
buffer[16] = 0;
|
||||
text_modules.set(buffer);
|
||||
} else {
|
||||
text_modules.set("Bad version");
|
||||
}
|
||||
}
|
||||
|
||||
button_makefile.on_select = [this](Button&){
|
||||
FATFS fs; /* Work area (file system object) for logical drives */
|
||||
FIL fdst; /* File objects */
|
||||
int16_t buffer[512]; /* File copy buffer */
|
||||
UINT bw; /* File read/write count */
|
||||
|
||||
sdcConnect(&SDCD1);
|
||||
|
||||
fr = f_mount(&fs, "", 1);
|
||||
|
||||
fr = f_open(&fdst, "TST.SND", FA_OPEN_EXISTING | FA_READ);
|
||||
|
||||
//if (!fr) led_rx.on();
|
||||
|
||||
/*fr = f_read(&fdst, buffer, 512*2, &bw);
|
||||
|
||||
Coord oy,ny;
|
||||
|
||||
oy = 128;
|
||||
|
||||
for (int c=0;c<512;c++) {
|
||||
ny = 128+32-(buffer[c]>>10);
|
||||
portapack::display.draw_line({static_cast<Coord>(c/3),oy},{static_cast<Coord>((c+1)/3),ny},{255,127,0});
|
||||
oy = ny;
|
||||
}*/
|
||||
|
||||
/*
|
||||
//if (fr) return;
|
||||
|
||||
fr = f_write(&fdst, buffer, br, &bw);
|
||||
//if (fr || bw < br) return;*/
|
||||
|
||||
//set_dirty();
|
||||
|
||||
f_close(&fdst);
|
||||
|
||||
f_mount(NULL, "", 0);
|
||||
|
||||
};
|
||||
|
||||
button_done.on_select = [&nav](Button&){ nav.pop(); };
|
||||
}
|
||||
|
||||
void DebugSDView::focus() {
|
||||
void RegistersView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
@@ -267,15 +297,30 @@ void DebugLCRView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* DebugMenuView *********************************************************/
|
||||
|
||||
DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
add_items<7>({ {
|
||||
{ "Memory", ui::Color::white(), [&nav](){ nav.push(new DebugMemoryView { nav }); } },
|
||||
{ "Radio State", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
{ "SD Card", ui::Color::white(), [&nav](){ nav.push(new DebugSDView { nav }); } },
|
||||
{ "RFFC5072", ui::Color::white(), [&nav](){ nav.push(new DebugRFFC5072View { nav }); } },
|
||||
{ "MAX2837", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
{ "Si5351C", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
{ "WM8731", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
add_items<8>({ {
|
||||
{ "Memory", [&nav](){ nav.push<DebugMemoryView>(); } },
|
||||
{ "Radio State", [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SD Card", [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "RFFC5072", [&nav](){ nav.push<RegistersView>(
|
||||
"RFFC5072", RegistersWidgetConfig { 31, 2, 4, 4 },
|
||||
[](const size_t register_number) { return radio::first_if.read(register_number); }
|
||||
); } },
|
||||
{ "MAX2837", [&nav](){ nav.push<RegistersView>(
|
||||
"MAX2837", RegistersWidgetConfig { 32, 2, 3, 4 },
|
||||
[](const size_t register_number) { return radio::second_if.read(register_number); }
|
||||
); } },
|
||||
{ "Si5351C", [&nav](){ nav.push<RegistersView>(
|
||||
"Si5351C", RegistersWidgetConfig { 96, 2, 2, 8 },
|
||||
[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }
|
||||
); } },
|
||||
{ "WM8731", [&nav](){ nav.push<RegistersView>(
|
||||
"WM8731", RegistersWidgetConfig { wolfson::wm8731::reg_count, 1, 3, 4 },
|
||||
[](const size_t register_number) { return portapack::audio_codec.read(register_number); }
|
||||
); } },
|
||||
{ "Temperature", [&nav](){ nav.push<TemperatureView>(); } },
|
||||
} });
|
||||
on_left = [&nav](){ nav.pop(); };
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "rffc507x.hpp"
|
||||
#include "max2837.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -79,67 +84,128 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class DebugRFFC5072RegistersWidget : public Widget {
|
||||
class TemperatureWidget : public Widget {
|
||||
public:
|
||||
constexpr DebugRFFC5072RegistersWidget(
|
||||
explicit constexpr TemperatureWidget(
|
||||
Rect parent_rect
|
||||
) : Widget { parent_rect }
|
||||
{
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
using sample_t = uint32_t;
|
||||
using temperature_t = int32_t;
|
||||
|
||||
temperature_t temperature(const sample_t sensor_value) const;
|
||||
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
|
||||
|
||||
std::string temperature_str(const temperature_t temperature) const;
|
||||
|
||||
static constexpr temperature_t display_temp_min = 0;
|
||||
static constexpr temperature_t display_temp_scale = 3;
|
||||
static constexpr int bar_width = 1;
|
||||
static constexpr int temp_len = 3;
|
||||
};
|
||||
|
||||
class TemperatureView : public View {
|
||||
public:
|
||||
explicit TemperatureView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
Text text_title {
|
||||
{ 76, 16, 240, 16 },
|
||||
"Temperature",
|
||||
};
|
||||
|
||||
TemperatureWidget temperature_widget {
|
||||
{ 0, 40, 240, 180 },
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 72, 264, 96, 24 },
|
||||
"Done"
|
||||
};
|
||||
};
|
||||
|
||||
struct RegistersWidgetConfig {
|
||||
int registers_count;
|
||||
int legend_length;
|
||||
int value_length;
|
||||
int registers_per_row;
|
||||
|
||||
constexpr int legend_width() const {
|
||||
return legend_length * 8;
|
||||
}
|
||||
|
||||
constexpr int value_width() const {
|
||||
return value_length * 8;
|
||||
}
|
||||
|
||||
constexpr int registers_row_length() const {
|
||||
return (registers_per_row * (value_length + 1)) - 1;
|
||||
}
|
||||
|
||||
constexpr int registers_row_width() const {
|
||||
return registers_row_length() * 8;
|
||||
}
|
||||
|
||||
constexpr int row_width() const {
|
||||
return legend_width() + 8 + registers_row_width();
|
||||
}
|
||||
|
||||
constexpr int rows() const {
|
||||
return registers_count / registers_per_row;
|
||||
}
|
||||
};
|
||||
|
||||
class RegistersWidget : public Widget {
|
||||
public:
|
||||
RegistersWidget(
|
||||
RegistersWidgetConfig&& config,
|
||||
std::function<uint32_t(const size_t register_number)>&& reader
|
||||
);
|
||||
|
||||
void update();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t registers_count { 31 };
|
||||
const RegistersWidgetConfig config;
|
||||
const std::function<uint32_t(const size_t register_number)> reader;
|
||||
|
||||
static constexpr size_t legend_length { 2 };
|
||||
static constexpr Dim legend_width { legend_length * 8 };
|
||||
static constexpr int row_height = 16;
|
||||
|
||||
static constexpr size_t value_length { 4 };
|
||||
static constexpr Dim value_width { value_length * 8 };
|
||||
|
||||
static constexpr size_t registers_per_row { 4 };
|
||||
static constexpr size_t registers_row_length {
|
||||
(registers_per_row * (value_length + 1)) - 1
|
||||
};
|
||||
static constexpr Dim registers_row_width {
|
||||
registers_row_length * 8
|
||||
};
|
||||
|
||||
static constexpr size_t rows {
|
||||
registers_count / registers_per_row
|
||||
};
|
||||
static constexpr Dim row_height { 16 };
|
||||
|
||||
void draw_legend(Painter& painter);
|
||||
void draw_values(Painter& painter, const rffc507x::RegisterMap registers);
|
||||
void draw_legend(const Coord left, Painter& painter);
|
||||
void draw_values(const Coord left, Painter& painter);
|
||||
};
|
||||
|
||||
class DebugRFFC5072View : public View {
|
||||
class RegistersView : public View {
|
||||
public:
|
||||
DebugRFFC5072View(NavigationView& nav);
|
||||
RegistersView(
|
||||
NavigationView& nav,
|
||||
const std::string& title,
|
||||
RegistersWidgetConfig&& config,
|
||||
std::function<uint32_t(const size_t register_number)>&& reader
|
||||
);
|
||||
|
||||
void focus() override;
|
||||
void focus();
|
||||
|
||||
private:
|
||||
Text text_title {
|
||||
{ 88, 16, 40, 16 },
|
||||
"RFFC5072",
|
||||
};
|
||||
Text text_title;
|
||||
|
||||
DebugRFFC5072RegistersWidget widget_registers {
|
||||
{ 32, 48, 176, 128 }
|
||||
};
|
||||
RegistersWidget registers_widget;
|
||||
|
||||
Button button_update {
|
||||
{ 16, 192, 96, 24 },
|
||||
{ 16, 256, 96, 24 },
|
||||
"Update"
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 128, 192, 96, 24 },
|
||||
{ 128, 256, 96, 24 },
|
||||
"Done"
|
||||
};
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -42,7 +43,10 @@
|
||||
#include "ui_sigfrx.hpp"
|
||||
#include "ui_numbers.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "ais_app.hpp"
|
||||
#include "ert_app.hpp"
|
||||
#include "tpms_app.hpp"
|
||||
|
||||
#include "m4_startup.hpp"
|
||||
#include "spi_image.hpp"
|
||||
|
||||
@@ -56,31 +60,78 @@ namespace ui {
|
||||
|
||||
SystemStatusView::SystemStatusView() {
|
||||
add_children({ {
|
||||
&portapack,
|
||||
&button_back,
|
||||
&title,
|
||||
&button_sleep,
|
||||
&sd_card_status_view,
|
||||
} });
|
||||
sd_card_status_view.set_parent_rect({ 28 * 8, 0 * 16, 2 * 8, 1 * 16 });
|
||||
|
||||
button_back.on_select = [this](Button&){
|
||||
if( this->on_back ) {
|
||||
this->on_back();
|
||||
}
|
||||
};
|
||||
|
||||
button_sleep.on_select = [this](Button&) {
|
||||
DisplaySleepMessage message;
|
||||
EventDispatcher::message_map().send(&message);
|
||||
};
|
||||
}
|
||||
|
||||
void SystemStatusView::set_back_visible(bool new_value) {
|
||||
button_back.hidden(!new_value);
|
||||
}
|
||||
|
||||
void SystemStatusView::set_title(const std::string new_value) {
|
||||
if( new_value.empty() ) {
|
||||
title.set(default_title);
|
||||
} else {
|
||||
title.set(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation ************************************************************/
|
||||
|
||||
NavigationView::NavigationView()
|
||||
{
|
||||
bool NavigationView::is_top() const {
|
||||
return view_stack.size() == 1;
|
||||
}
|
||||
|
||||
void NavigationView::push(View* new_view) {
|
||||
// TODO: Trap nullptr?
|
||||
// TODO: Trap push of object already on stack?
|
||||
view_stack.push_back(new_view);
|
||||
set_view(new_view);
|
||||
View* NavigationView::push_view(std::unique_ptr<View> new_view) {
|
||||
free_view();
|
||||
|
||||
const auto p = new_view.get();
|
||||
view_stack.emplace_back(std::move(new_view));
|
||||
|
||||
update_view();
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void NavigationView::pop() {
|
||||
// Can't pop last item from stack.
|
||||
if( view_stack.size() > 1 ) {
|
||||
const auto old_view = view_stack.back();
|
||||
free_view();
|
||||
|
||||
view_stack.pop_back();
|
||||
const auto new_view = view_stack.back();
|
||||
set_view(new_view);
|
||||
delete old_view;
|
||||
|
||||
update_view();
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationView::free_view() {
|
||||
remove_child(view());
|
||||
}
|
||||
|
||||
void NavigationView::update_view() {
|
||||
const auto new_view = view_stack.back().get();
|
||||
add_child(new_view);
|
||||
new_view->set_parent_rect({ {0, 0}, size() });
|
||||
focus();
|
||||
set_dirty();
|
||||
|
||||
if( on_view_changed ) {
|
||||
on_view_changed(*new_view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,51 +139,53 @@ Widget* NavigationView::view() const {
|
||||
return children_.empty() ? nullptr : children_[0];
|
||||
}
|
||||
|
||||
void NavigationView::set_view(Widget* const new_view) {
|
||||
const auto old_view = view();
|
||||
if( old_view ) {
|
||||
remove_child(old_view);
|
||||
}
|
||||
|
||||
// TODO: Allow new_view == nullptr?!
|
||||
if( new_view ) {
|
||||
add_child(new_view);
|
||||
new_view->set_parent_rect({ {0, 0}, size() });
|
||||
focus();
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void NavigationView::focus() {
|
||||
if( view() ) {
|
||||
view()->focus();
|
||||
}
|
||||
}
|
||||
|
||||
/* TransceiversMenuView **************************************************/
|
||||
|
||||
TranspondersMenuView::TranspondersMenuView(NavigationView& nav) {
|
||||
add_items<3>({ {
|
||||
{ "AIS: Boats", [&nav](){ nav.push<AISAppView>(); } },
|
||||
{ "ERT: Utility Meters", [&nav](){ nav.push<ERTAppView>(); } },
|
||||
{ "TPMS: Cars", [&nav](){ nav.push<TPMSAppView>(); } },
|
||||
} });
|
||||
}
|
||||
|
||||
/* ReceiverMenuView ******************************************************/
|
||||
|
||||
ReceiverMenuView::ReceiverMenuView(NavigationView& nav) {
|
||||
add_items<2>({ {
|
||||
{ "Audio", [&nav](){ nav.push<ReceiverView>(); } },
|
||||
{ "Transponders", [&nav](){ nav.push<TranspondersMenuView>(); } },
|
||||
} });
|
||||
}
|
||||
|
||||
/* SystemMenuView ********************************************************/
|
||||
|
||||
SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
add_items<10>({ {
|
||||
{ "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } },
|
||||
{ "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverView { nav, receiver_model }}); } },
|
||||
{ "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } },
|
||||
{ "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverMenuView { nav, receiver_model }}); } },
|
||||
//{ "Nordic/BTLE RX", ui::Color::cyan(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
{ "Jammer", ui::Color::white(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new JammerView { nav, transmitter_model }}); } },
|
||||
{ "Jammer", ui::Color::white(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new JammerView { nav, transmitter_model }}); } },
|
||||
//{ "Audio file TX", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
//{ "Encoder TX", ui::Color::green(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
|
||||
//{ "Whistle", ui::Color::purple(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new WhistleView { nav, transmitter_model }}); } },
|
||||
//{ "SIGFOX RX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new SIGFRXView { nav, receiver_model }}); } },
|
||||
{ "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new RDSView { nav, transmitter_model }}); } },
|
||||
{ "Xylos TX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosView { nav, transmitter_model }}); } },
|
||||
//{ "SIGFOX RX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new SIGFRXView { nav, receiver_model }}); } },
|
||||
{ "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new RDSView { nav, transmitter_model }}); } },
|
||||
{ "Xylos TX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosView { nav, transmitter_model }}); } },
|
||||
//{ "Xylos RX", ui::Color::green(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosRXView { nav, receiver_model }}); } },
|
||||
//{ "AFSK RX", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new AFSKRXView { nav, receiver_model }}); } },
|
||||
{ "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new LCRView { nav, transmitter_model }}); } },
|
||||
//{ "Numbers station", ui::Color::purple(),[&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new NumbersStationView { nav, transmitter_model }}); } },
|
||||
|
||||
{ "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } },
|
||||
{ "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav, transmitter_model }); } },
|
||||
{ "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } },
|
||||
{ "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } },
|
||||
//{ "AFSK RX", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new AFSKRXView { nav, receiver_model }}); } },
|
||||
{ "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new LCRView { nav, transmitter_model }}); } },
|
||||
//{ "Numbers station", ui::Color::purple(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new NumbersStationView { nav, transmitter_model }}); } },
|
||||
{ "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } },
|
||||
{ "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav, transmitter_model }); } },
|
||||
{ "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } },
|
||||
{ "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } },
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -159,12 +212,19 @@ SystemView::SystemView(
|
||||
{ 0, 0 },
|
||||
{ parent_rect.width(), status_view_height }
|
||||
});
|
||||
status_view.on_back = [this]() {
|
||||
this->navigation_view.pop();
|
||||
};
|
||||
|
||||
add_child(&navigation_view);
|
||||
navigation_view.set_parent_rect({
|
||||
{ 0, status_view_height },
|
||||
{ parent_rect.width(), static_cast<ui::Dim>(parent_rect.height() - status_view_height) }
|
||||
});
|
||||
navigation_view.on_view_changed = [this](const View& new_view) {
|
||||
this->status_view.set_back_visible(!this->navigation_view.is_top());
|
||||
this->status_view.set_title(new_view.title());
|
||||
};
|
||||
|
||||
// Initial view.
|
||||
// TODO: Restore from non-volatile memory?
|
||||
@@ -175,73 +235,15 @@ SystemView::SystemView(
|
||||
// navigation_view.push(new BMPView { navigation_view });
|
||||
|
||||
if (portapack::persistent_memory::ui_config() & 1)
|
||||
navigation_view.push(new BMPView { navigation_view });
|
||||
navigation_view.push<BMPView>();
|
||||
else
|
||||
navigation_view.push(new SystemMenuView { navigation_view });
|
||||
navigation_view.push<SystemMenuView>();
|
||||
}
|
||||
|
||||
Context& SystemView::context() const {
|
||||
return context_;
|
||||
}
|
||||
|
||||
/* ***********************************************************************/
|
||||
|
||||
void BMPView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
BMPView::BMPView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_info,
|
||||
&button_done
|
||||
} });
|
||||
|
||||
button_done.on_select = [this,&nav](Button&){
|
||||
nav.pop();
|
||||
nav.push(new SystemMenuView { nav });
|
||||
};
|
||||
}
|
||||
|
||||
void BMPView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
portapack::display.drawBMP({(240-185)/2, 0}, splash_bmp);
|
||||
}
|
||||
|
||||
/* PlayDeadView **********************************************************/
|
||||
|
||||
void PlayDeadView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
PlayDeadView::PlayDeadView(NavigationView& nav, bool booting) {
|
||||
_booting = booting;
|
||||
persistent_memory::set_playing_dead(0x59);
|
||||
|
||||
add_children({ {
|
||||
&text_playdead1,
|
||||
&text_playdead2,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_done.on_dir = [this,&nav](Button&, KeyEvent key){
|
||||
sequence = (sequence<<3) | static_cast<std::underlying_type<KeyEvent>::type>(key);
|
||||
};
|
||||
|
||||
button_done.on_select = [this,&nav](Button&){
|
||||
if (sequence == persistent_memory::playdead_sequence()) {
|
||||
persistent_memory::set_playing_dead(0);
|
||||
if (_booting) {
|
||||
nav.pop();
|
||||
nav.push(new SystemMenuView { nav });
|
||||
} else {
|
||||
nav.pop();
|
||||
}
|
||||
} else {
|
||||
sequence = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* HackRFFirmwareView ****************************************************/
|
||||
|
||||
HackRFFirmwareView::HackRFFirmwareView(NavigationView& nav) {
|
||||
|
||||
@@ -30,44 +30,76 @@
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_channel.hpp"
|
||||
#include "ui_audio.hpp"
|
||||
#include "ui_sd_card_status_view.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SystemStatusView : public View {
|
||||
public:
|
||||
std::function<void(void)> on_back;
|
||||
|
||||
SystemStatusView();
|
||||
|
||||
void set_back_visible(bool new_value);
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
private:
|
||||
Text portapack {
|
||||
{ 0, 0, 15 * 8, 1 * 16 },
|
||||
"PortaPack/HAVOC"
|
||||
static constexpr auto default_title = "PortaPack/HAVOC";
|
||||
|
||||
Button button_back {
|
||||
{ 0 * 8, 0 * 16, 3 * 8, 16 },
|
||||
" < ",
|
||||
};
|
||||
|
||||
Text title {
|
||||
{ 3 * 8, 0, 16 * 8, 1 * 16 },
|
||||
default_title,
|
||||
};
|
||||
|
||||
Button button_sleep {
|
||||
{ 25 * 8, 0, 2 * 8, 1 * 16 },
|
||||
"ZZ",
|
||||
};
|
||||
|
||||
SDCardStatusView sd_card_status_view;
|
||||
};
|
||||
|
||||
class NavigationView : public View {
|
||||
public:
|
||||
NavigationView();
|
||||
std::function<void(const View&)> on_view_changed;
|
||||
|
||||
NavigationView() { }
|
||||
|
||||
NavigationView(const NavigationView&) = delete;
|
||||
NavigationView(NavigationView&&) = delete;
|
||||
|
||||
void push(View* new_view);
|
||||
bool is_top() const;
|
||||
|
||||
template<class T, class... Args>
|
||||
T* push(Args&&... args) {
|
||||
return reinterpret_cast<T*>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
void pop();
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::vector<View*> view_stack;
|
||||
std::vector<std::unique_ptr<View>> view_stack;
|
||||
|
||||
Widget* view() const;
|
||||
void set_view(Widget* const new_view);
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
};
|
||||
|
||||
class SystemMenuView : public MenuView {
|
||||
class TranspondersMenuView : public MenuView {
|
||||
public:
|
||||
SystemMenuView(NavigationView& nav);
|
||||
TranspondersMenuView(NavigationView& nav);
|
||||
};
|
||||
|
||||
class BMPView : public View {
|
||||
@@ -88,6 +120,15 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class ReceiverMenuView : public MenuView {
|
||||
public:
|
||||
ReceiverMenuView(NavigationView& nav);
|
||||
};
|
||||
|
||||
class SystemMenuView : public MenuView {
|
||||
public:
|
||||
SystemMenuView(NavigationView& nav);
|
||||
};
|
||||
|
||||
class SystemView : public View {
|
||||
public:
|
||||
@@ -104,29 +145,6 @@ private:
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
class PlayDeadView : public View {
|
||||
public:
|
||||
PlayDeadView(NavigationView& nav, bool booting);
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
bool _booting;
|
||||
uint32_t sequence = 0;
|
||||
Text text_playdead1 {
|
||||
{ 6 * 8, 7 * 16, 14 * 8, 16 },
|
||||
"Firmware error"
|
||||
};
|
||||
Text text_playdead2 {
|
||||
{ 6 * 8, 9 * 16, 16 * 8, 16 },
|
||||
"0x1400_0000 : 2C"
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 240, 0, 1, 1 },
|
||||
""
|
||||
};
|
||||
};
|
||||
|
||||
class HackRFFirmwareView : public View {
|
||||
public:
|
||||
HackRFFirmwareView(NavigationView& nav);
|
||||
|
||||
@@ -21,48 +21,19 @@
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_console.hpp"
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "ais_baseband.hpp"
|
||||
#include "m4_startup.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "ais_app.hpp"
|
||||
#include "tpms_app.hpp"
|
||||
#include "ert_app.hpp"
|
||||
#include "spectrum_analysis_app.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* BasebandBandwidthField ************************************************/
|
||||
|
||||
BasebandBandwidthField::BasebandBandwidthField(
|
||||
Point parent_pos
|
||||
) : OptionsField {
|
||||
parent_pos,
|
||||
4,
|
||||
{
|
||||
{ " 1M8", 1750000 },
|
||||
{ " 2M5", 2500000 },
|
||||
{ " 3M5", 3500000 },
|
||||
{ " 5M ", 5000000 },
|
||||
{ " 5M5", 5500000 },
|
||||
{ " 6M ", 6000000 },
|
||||
{ " 7M ", 7000000 },
|
||||
{ " 8M ", 8000000 },
|
||||
{ " 9M ", 9000000 },
|
||||
{ "10M ", 10000000 },
|
||||
{ "12M ", 12000000 },
|
||||
{ "14M ", 14000000 },
|
||||
{ "15M ", 15000000 },
|
||||
{ "20M ", 20000000 },
|
||||
{ "24M ", 24000000 },
|
||||
{ "28M ", 28000000 },
|
||||
}
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
/* FrequencyField ********************************************************/
|
||||
|
||||
FrequencyField::FrequencyField(
|
||||
@@ -127,25 +98,7 @@ bool FrequencyField::on_key(const ui::KeyEvent event) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
bool FrequencyField::on_key(const ui::KeyEvent event) override {
|
||||
if( event == ui::KeyEvent::Select ) {
|
||||
|
||||
// NOTE: For testing sampling rate / decimation combinations
|
||||
turbo = !turbo;
|
||||
if( turbo ) {
|
||||
clock_manager.set_sampling_frequency(18432000);
|
||||
radio::set_baseband_decimation_by(6);
|
||||
} else {
|
||||
clock_manager.set_sampling_frequency(12288000);
|
||||
radio::set_baseband_decimation_by(4);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
bool FrequencyField::on_encoder(const EncoderEvent delta) {
|
||||
set_value(value() + (delta * step));
|
||||
return true;
|
||||
@@ -165,13 +118,7 @@ void FrequencyField::on_focus() {
|
||||
}
|
||||
|
||||
rf::Frequency FrequencyField::clamp_value(rf::Frequency value) {
|
||||
if( value > range.max ) {
|
||||
value = range.max;
|
||||
}
|
||||
if( value < range.min ) {
|
||||
value = range.min;
|
||||
}
|
||||
return value;
|
||||
return range.clip(value);
|
||||
}
|
||||
|
||||
/* FrequencyKeypadView ***************************************************/
|
||||
@@ -188,7 +135,7 @@ FrequencyKeypadView::FrequencyKeypadView(
|
||||
|
||||
const char* const key_caps = "123456789<0.";
|
||||
|
||||
size_t n = 0;
|
||||
int n = 0;
|
||||
for(auto& button : buttons) {
|
||||
add_child(&button);
|
||||
const std::string label {
|
||||
@@ -196,8 +143,8 @@ FrequencyKeypadView::FrequencyKeypadView(
|
||||
};
|
||||
button.on_select = button_fn;
|
||||
button.set_parent_rect({
|
||||
static_cast<Coord>((n % 3) * button_w),
|
||||
static_cast<Coord>((n / 3) * button_h + button_h),
|
||||
(n % 3) * button_w,
|
||||
(n / 3) * button_h + button_h,
|
||||
button_w, button_h
|
||||
});
|
||||
button.set_text(label);
|
||||
@@ -301,7 +248,6 @@ FrequencyOptionsView::FrequencyOptionsView(
|
||||
add_children({ {
|
||||
&text_step,
|
||||
&options_step,
|
||||
&text_correction,
|
||||
&field_ppm,
|
||||
&text_ppm,
|
||||
} });
|
||||
@@ -339,19 +285,11 @@ RadioGainOptionsView::RadioGainOptionsView(
|
||||
add_children({ {
|
||||
&label_rf_amp,
|
||||
&field_rf_amp,
|
||||
//&label_agc,
|
||||
//&field_agc
|
||||
} });
|
||||
|
||||
field_rf_amp.on_change = [this](int32_t v) {
|
||||
this->on_rf_amp_changed(v);
|
||||
};
|
||||
/*
|
||||
field_agc.set_value(receiver_model.agc());
|
||||
field_agc.on_change = [this](int32_t v) {
|
||||
this->on_agc_changed(v);
|
||||
};
|
||||
*/
|
||||
}
|
||||
|
||||
void RadioGainOptionsView::set_rf_amp(int32_t v_db) {
|
||||
@@ -363,19 +301,14 @@ void RadioGainOptionsView::on_rf_amp_changed(bool enable) {
|
||||
on_change_rf_amp(enable);
|
||||
}
|
||||
}
|
||||
/*
|
||||
void RadioGainOptionsView::on_agc_changed(bool v) {
|
||||
receiver_model.set_agc(v);
|
||||
}
|
||||
*/
|
||||
|
||||
/* LNAGainField **********************************************************/
|
||||
|
||||
LNAGainField::LNAGainField(
|
||||
Point parent_pos
|
||||
) : NumberField {
|
||||
{ parent_pos }, 2,
|
||||
{ max2837::lna::gain_db_min, max2837::lna::gain_db_max },
|
||||
parent_pos, 2,
|
||||
{ max2837::lna::gain_db_range.minimum, max2837::lna::gain_db_range.maximum },
|
||||
max2837::lna::gain_db_step,
|
||||
' ',
|
||||
}
|
||||
@@ -392,30 +325,21 @@ void LNAGainField::on_focus() {
|
||||
/* ReceiverView **********************************************************/
|
||||
|
||||
ReceiverView::ReceiverView(
|
||||
NavigationView& nav,
|
||||
ReceiverModel& receiver_model
|
||||
) : receiver_model(receiver_model)
|
||||
{
|
||||
NavigationView& nav
|
||||
) {
|
||||
add_children({ {
|
||||
&rssi,
|
||||
&channel,
|
||||
&audio,
|
||||
&button_done,
|
||||
&field_frequency,
|
||||
&field_lna,
|
||||
//&options_baseband_bandwidth,
|
||||
&field_vga,
|
||||
&options_modulation,
|
||||
//&options_baseband_oversampling,
|
||||
&field_volume,
|
||||
&view_frequency_options,
|
||||
&view_rf_gain_options,
|
||||
} });
|
||||
|
||||
button_done.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
field_frequency.set_value(receiver_model.tuning_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
@@ -423,12 +347,11 @@ ReceiverView::ReceiverView(
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = new FrequencyKeypadView { nav, this->receiver_model.tuning_frequency() };
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_tuning_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
nav.push(new_view);
|
||||
};
|
||||
field_frequency.on_show_options = [this]() {
|
||||
this->on_show_options_frequency();
|
||||
@@ -441,13 +364,7 @@ ReceiverView::ReceiverView(
|
||||
field_lna.on_show_options = [this]() {
|
||||
this->on_show_options_rf_gain();
|
||||
};
|
||||
/*
|
||||
options_baseband_bandwidth.set_by_value(receiver_model.baseband_bandwidth());
|
||||
options_baseband_bandwidth.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
(void)n;
|
||||
this->on_baseband_bandwidth_changed(v);
|
||||
};
|
||||
*/
|
||||
|
||||
field_vga.set_value(receiver_model.vga());
|
||||
field_vga.on_change = [this](int32_t v_db) {
|
||||
this->on_vga_changed(v_db);
|
||||
@@ -456,15 +373,9 @@ ReceiverView::ReceiverView(
|
||||
options_modulation.set_by_value(receiver_model.modulation());
|
||||
options_modulation.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
(void)n;
|
||||
this->on_modulation_changed((mode_type)v);
|
||||
this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
|
||||
};
|
||||
/*
|
||||
options_baseband_oversampling.set_by_value(receiver_model.baseband_oversampling());
|
||||
options_baseband_oversampling.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
(void)n;
|
||||
this->on_baseband_oversampling_changed(v);
|
||||
};
|
||||
*/
|
||||
|
||||
field_volume.set_value((receiver_model.headphone_volume() - wolfson::wm8731::headphone_gain_range.max).decibel() + 99);
|
||||
field_volume.on_change = [this](int32_t v) {
|
||||
this->on_headphone_volume_changed(v);
|
||||
@@ -499,110 +410,20 @@ ReceiverView::~ReceiverView() {
|
||||
}
|
||||
|
||||
void ReceiverView::on_show() {
|
||||
auto& message_map = context().message_map();
|
||||
message_map.register_handler(Message::ID::AISPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const AISPacketMessage*>(p);
|
||||
this->on_packet_ais(*message);
|
||||
}
|
||||
);
|
||||
message_map.register_handler(Message::ID::TPMSPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const TPMSPacketMessage*>(p);
|
||||
this->on_packet_tpms(*message);
|
||||
}
|
||||
);
|
||||
message_map.register_handler(Message::ID::SDCardStatus,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const SDCardStatusMessage*>(p);
|
||||
this->on_sd_card_mounted(message->is_mounted);
|
||||
}
|
||||
);
|
||||
View::on_show();
|
||||
|
||||
// TODO: Separate concepts of baseband "modulation" and receiver "mode".
|
||||
on_modulation_changed(static_cast<ReceiverModel::Mode>(receiver_model.modulation()));
|
||||
}
|
||||
|
||||
void ReceiverView::on_hide() {
|
||||
auto& message_map = context().message_map();
|
||||
message_map.unregister_handler(Message::ID::SDCardStatus);
|
||||
message_map.unregister_handler(Message::ID::TPMSPacket);
|
||||
message_map.unregister_handler(Message::ID::AISPacket);
|
||||
}
|
||||
on_modulation_changed(static_cast<ReceiverModel::Mode>(-1));
|
||||
|
||||
void ReceiverView::on_packet_ais(const AISPacketMessage& message) {
|
||||
const auto result = baseband::ais::packet_decode(message.packet.payload, message.packet.bits_received);
|
||||
|
||||
auto console = reinterpret_cast<Console*>(widget_content.get());
|
||||
if( result.first == "OK" ) {
|
||||
console->writeln(result.second);
|
||||
}
|
||||
}
|
||||
|
||||
static FIL fil_tpms;
|
||||
|
||||
void ReceiverView::on_packet_tpms(const TPMSPacketMessage& message) {
|
||||
auto payload = message.packet.payload;
|
||||
auto payload_length = message.packet.bits_received;
|
||||
|
||||
std::string hex_data;
|
||||
std::string hex_error;
|
||||
uint8_t byte_data = 0;
|
||||
uint8_t byte_error = 0;
|
||||
for(size_t i=0; i<payload_length; i+=2) {
|
||||
const auto bit_data = payload[i+1];
|
||||
const auto bit_error = (payload[i+0] == payload[i+1]);
|
||||
|
||||
byte_data <<= 1;
|
||||
byte_data |= bit_data ? 1 : 0;
|
||||
|
||||
byte_error <<= 1;
|
||||
byte_error |= bit_error ? 1 : 0;
|
||||
|
||||
if( ((i >> 1) & 7) == 7 ) {
|
||||
hex_data += to_string_hex(byte_data, 2);
|
||||
hex_error += to_string_hex(byte_error, 2);
|
||||
}
|
||||
}
|
||||
|
||||
auto console = reinterpret_cast<Console*>(widget_content.get());
|
||||
console->writeln(hex_data.substr(0, 240 / 8));
|
||||
|
||||
if( !f_error(&fil_tpms) ) {
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
std::string timestamp =
|
||||
to_string_dec_uint(datetime.year(), 4) +
|
||||
to_string_dec_uint(datetime.month(), 2, '0') +
|
||||
to_string_dec_uint(datetime.day(), 2, '0') +
|
||||
to_string_dec_uint(datetime.hour(), 2, '0') +
|
||||
to_string_dec_uint(datetime.minute(), 2, '0') +
|
||||
to_string_dec_uint(datetime.second(), 2, '0');
|
||||
|
||||
const auto tuning_frequency = receiver_model.tuning_frequency();
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10);
|
||||
|
||||
std::string log = timestamp + " " + tuning_frequency_str + " FSK 38.4 19.2 " + hex_data + "/" + hex_error + "\r\n";
|
||||
f_puts(log.c_str(), &fil_tpms);
|
||||
f_sync(&fil_tpms);
|
||||
}
|
||||
}
|
||||
|
||||
void ReceiverView::on_sd_card_mounted(const bool is_mounted) {
|
||||
if( is_mounted ) {
|
||||
const auto open_result = f_open(&fil_tpms, "tpms.txt", FA_WRITE | FA_OPEN_ALWAYS);
|
||||
if( open_result == FR_OK ) {
|
||||
const auto fil_size = f_size(&fil_tpms);
|
||||
const auto seek_result = f_lseek(&fil_tpms, fil_size);
|
||||
if( seek_result != FR_OK ) {
|
||||
f_close(&fil_tpms);
|
||||
}
|
||||
} else {
|
||||
// TODO: Error, indicate somehow.
|
||||
}
|
||||
}
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void ReceiverView::focus() {
|
||||
button_done.focus();
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void ReceiverView::on_tuning_frequency_changed(rf::Frequency f) {
|
||||
@@ -625,56 +446,27 @@ void ReceiverView::on_vga_changed(int32_t v_db) {
|
||||
receiver_model.set_vga(v_db);
|
||||
}
|
||||
|
||||
void ReceiverView::on_modulation_changed(mode_type modulation) {
|
||||
/* TODO: This is TERRIBLE!!! */
|
||||
switch(modulation) {
|
||||
case 3:
|
||||
case 5:
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = modulation,
|
||||
.sampling_rate = 2457600,
|
||||
.decimation_factor = 4,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = modulation,
|
||||
.sampling_rate = 20000000,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(12000000);
|
||||
break;
|
||||
|
||||
default:
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = modulation,
|
||||
.sampling_rate = 3072000,
|
||||
.decimation_factor = 4,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
break;
|
||||
}
|
||||
|
||||
void ReceiverView::on_modulation_changed(ReceiverModel::Mode mode) {
|
||||
remove_child(widget_content.get());
|
||||
widget_content.reset();
|
||||
|
||||
switch(modulation) {
|
||||
case 3:
|
||||
case 5:
|
||||
widget_content = std::make_unique<Console>();
|
||||
add_child(widget_content.get());
|
||||
switch(mode) {
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
widget_content = std::make_unique<AnalogAudioView>(mode);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
widget_content = std::make_unique<SpectrumAnalysisView>();
|
||||
break;
|
||||
|
||||
default:
|
||||
widget_content = std::make_unique<spectrum::WaterfallWidget>();
|
||||
add_child(widget_content.get());
|
||||
break;
|
||||
}
|
||||
|
||||
if( widget_content ) {
|
||||
const ui::Dim header_height = 3 * 16;
|
||||
add_child(widget_content.get());
|
||||
const ui::Rect rect { 0, header_height, parent_rect.width(), static_cast<ui::Dim>(parent_rect.height() - header_height) };
|
||||
widget_content->set_parent_rect(rect);
|
||||
}
|
||||
@@ -710,8 +502,4 @@ void ReceiverView::on_headphone_volume_changed(int32_t v) {
|
||||
receiver_model.set_headphone_volume(new_volume);
|
||||
}
|
||||
|
||||
// void ReceiverView::on_baseband_oversampling_changed(int32_t v) {
|
||||
// receiver_model.set_baseband_oversampling(v);
|
||||
// }
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -44,11 +44,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
class BasebandBandwidthField : public OptionsField {
|
||||
public:
|
||||
BasebandBandwidthField(Point parent_pos);
|
||||
};
|
||||
|
||||
class FrequencyField : public Widget {
|
||||
public:
|
||||
std::function<void(rf::Frequency)> on_change;
|
||||
@@ -77,8 +72,6 @@ private:
|
||||
rf::Frequency value_;
|
||||
rf::Frequency step { 25000 };
|
||||
|
||||
//bool turbo { false };
|
||||
|
||||
rf::Frequency clamp_value(rf::Frequency value);
|
||||
};
|
||||
|
||||
@@ -118,13 +111,6 @@ public:
|
||||
}
|
||||
|
||||
void add_digit(const char c) {
|
||||
/*
|
||||
if( justify == Justify::Right ) {
|
||||
push_right(c);
|
||||
} else {
|
||||
insert_right(c);
|
||||
}
|
||||
*/
|
||||
insert_right(c);
|
||||
}
|
||||
|
||||
@@ -209,16 +195,15 @@ public:
|
||||
void set_value(const rf::Frequency new_value);
|
||||
|
||||
private:
|
||||
static constexpr size_t button_w = 240 / 3;
|
||||
static constexpr size_t button_h = 48;
|
||||
static constexpr int button_w = 240 / 3;
|
||||
static constexpr int button_h = 48;
|
||||
|
||||
static constexpr size_t mhz_digits = 4;
|
||||
static constexpr size_t submhz_digits = 4;
|
||||
static constexpr int mhz_digits = 4;
|
||||
static constexpr int submhz_digits = 4;
|
||||
|
||||
static constexpr size_t mhz_mod = pow(10, mhz_digits);
|
||||
static constexpr size_t submhz_base = pow(10, 6 - submhz_digits);
|
||||
//static constexpr size_t submhz_mod = pow(10, submhz_digits);
|
||||
static constexpr size_t text_digits = mhz_digits + 1 + submhz_digits;
|
||||
static constexpr int mhz_mod = pow(10, mhz_digits);
|
||||
static constexpr int submhz_base = pow(10, 6 - submhz_digits);
|
||||
static constexpr int text_digits = mhz_digits + 1 + submhz_digits;
|
||||
|
||||
Text text_value {
|
||||
{ 0, 0, text_digits * button_w, button_h }
|
||||
@@ -274,6 +259,10 @@ private:
|
||||
{
|
||||
{ " 100", 100 },
|
||||
{ " 1k ", 1000 },
|
||||
{ " 3k ", 3000 }, /* Approximate SSB bandwidth */
|
||||
{ " 5k ", 5000 },
|
||||
{ " 6k3", 6250 },
|
||||
{ " 9k ", 9000 }, /* channel spacing for LF, MF in some regions */
|
||||
{ " 10k ", 10000 },
|
||||
{ " 12k5", 12500 },
|
||||
{ " 25k ", 25000 },
|
||||
@@ -286,11 +275,6 @@ private:
|
||||
void on_step_changed(rf::Frequency v);
|
||||
void on_reference_ppm_correction_changed(int32_t v);
|
||||
|
||||
Text text_correction {
|
||||
{ 17 * 8, 0 * 16, 5 * 8, 16 },
|
||||
"Corr.",
|
||||
};
|
||||
|
||||
NumberField field_ppm {
|
||||
{ 23 * 8, 0 * 16 },
|
||||
3,
|
||||
@@ -326,18 +310,6 @@ private:
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
/*
|
||||
Text label_agc {
|
||||
{ 6 * 8, 0 * 16, 3 * 8, 1 * 16 },
|
||||
"AGC"
|
||||
};
|
||||
|
||||
NumberField field_agc {
|
||||
{ 10 * 8, 0 * 16},
|
||||
1,
|
||||
{ 0, 1 }
|
||||
};
|
||||
*/
|
||||
|
||||
void on_rf_amp_changed(bool enable);
|
||||
};
|
||||
@@ -359,87 +331,58 @@ constexpr Style style_options_group {
|
||||
|
||||
class ReceiverView : public View {
|
||||
public:
|
||||
ReceiverView(NavigationView& nav, ReceiverModel& receiver_model);
|
||||
ReceiverView(NavigationView& nav);
|
||||
~ReceiverView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
ReceiverModel& receiver_model;
|
||||
static constexpr ui::Dim header_height = 2 * 16;
|
||||
|
||||
RSSI rssi {
|
||||
{ 19 * 8, 0, 11 * 8, 4 },
|
||||
{ 21 * 8, 0, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
Channel channel {
|
||||
{ 19 * 8, 5, 11 * 8, 4 },
|
||||
{ 21 * 8, 5, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
Audio audio {
|
||||
{ 19 * 8, 10, 11 * 8, 4 },
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 0 * 8, 0 * 16, 3 * 8, 16 },
|
||||
" < ",
|
||||
{ 21 * 8, 10, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
FrequencyField field_frequency {
|
||||
{ 0 * 8, 1 * 16 },
|
||||
{ 5 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
{ 13 * 8, 1 * 16 }
|
||||
{ 15 * 8, 0 * 16 }
|
||||
};
|
||||
/*
|
||||
BasebandBandwidthField options_baseband_bandwidth {
|
||||
{ 15 * 8, 1 * 16 },
|
||||
};
|
||||
*/
|
||||
|
||||
NumberField field_vga {
|
||||
{ 16 * 8, 1 * 16},
|
||||
{ 18 * 8, 0 * 16},
|
||||
2,
|
||||
{ max2837::vga::gain_db_min, max2837::vga::gain_db_max },
|
||||
{ max2837::vga::gain_db_range.minimum, max2837::vga::gain_db_range.maximum },
|
||||
max2837::vga::gain_db_step,
|
||||
' ',
|
||||
};
|
||||
|
||||
OptionsField options_modulation {
|
||||
{ 19 * 8, 1 * 16 },
|
||||
{ 0 * 8, 0 * 16 },
|
||||
4,
|
||||
{
|
||||
{ " AM ", RX_NBAM_AUDIO },
|
||||
{ "NFM ", RX_NBFM_AUDIO },
|
||||
{ "WFM ", RX_WBFM_AUDIO },
|
||||
{ "AIS ", RX_AIS },
|
||||
{ "TPMS", RX_TPMS },
|
||||
{ "SPEC", RX_WBSPECTRUM },
|
||||
{ " AM ", toUType(ReceiverModel::Mode::AMAudio) },
|
||||
{ "NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio) },
|
||||
{ "WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio) },
|
||||
{ "SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis) },
|
||||
}
|
||||
};
|
||||
/*
|
||||
OptionsField options_baseband_oversampling {
|
||||
{ 24 * 8, 1 * 16 },
|
||||
1,
|
||||
{
|
||||
{ "4", 4 },
|
||||
{ "6", 6 },
|
||||
{ "8", 8 },
|
||||
}
|
||||
};
|
||||
*/
|
||||
NumberField field_vregmode {
|
||||
{ 24 * 8, 1 * 16 },
|
||||
1,
|
||||
{ 0, 1 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 28 * 8, 1 * 16 },
|
||||
{ 28 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
@@ -447,12 +390,12 @@ private:
|
||||
};
|
||||
|
||||
FrequencyOptionsView view_frequency_options {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
||||
&style_options_group
|
||||
};
|
||||
|
||||
RadioGainOptionsView view_rf_gain_options {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
||||
&style_options_group
|
||||
};
|
||||
|
||||
@@ -463,18 +406,13 @@ private:
|
||||
void on_rf_amp_changed(bool v);
|
||||
void on_lna_changed(int32_t v_db);
|
||||
void on_vga_changed(int32_t v_db);
|
||||
void on_modulation_changed(mode_type modulation);
|
||||
void on_modulation_changed(ReceiverModel::Mode mode);
|
||||
void on_show_options_frequency();
|
||||
void on_show_options_rf_gain();
|
||||
void on_frequency_step_changed(rf::Frequency f);
|
||||
void on_reference_ppm_correction_changed(int32_t v);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
// void on_baseband_oversampling_changed(int32_t v);
|
||||
void on_edit_frequency();
|
||||
|
||||
void on_packet_ais(const AISPacketMessage& message);
|
||||
void on_packet_tpms(const TPMSPacketMessage& message);
|
||||
void on_sd_card_mounted(const bool is_mounted);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_rssi.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void RSSI::on_show() {
|
||||
context().message_map().register_handler(Message::ID::RSSIStatistics,
|
||||
EventDispatcher::message_map().register_handler(Message::ID::RSSIStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const RSSIStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
@@ -34,27 +38,29 @@ void RSSI::on_show() {
|
||||
}
|
||||
|
||||
void RSSI::on_hide() {
|
||||
context().message_map().unregister_handler(Message::ID::RSSIStatistics);
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::RSSIStatistics);
|
||||
}
|
||||
|
||||
void RSSI::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
/*
|
||||
constexpr int32_t rssi_min = 0.# * 256 / 3.3;
|
||||
constexpr int32_t rssi_max = 2.5 * 256 / 3.3;
|
||||
// (23 - 194) / 2
|
||||
*/
|
||||
/* TODO: Clip maximum */
|
||||
constexpr int32_t raw_min = 23;
|
||||
const int32_t x_0 = 0;
|
||||
const int32_t x_min = std::max(x_0, (min_ - raw_min) / 2);
|
||||
const int32_t x_avg = std::max(x_min, (avg_ - raw_min) / 2);
|
||||
const int32_t x_max = std::max(x_avg + 1, (max_ - raw_min) / 2);
|
||||
const int32_t x_lim = r.width();
|
||||
|
||||
constexpr int rssi_sample_range = 256;
|
||||
constexpr float rssi_voltage_min = 0.4;
|
||||
constexpr float rssi_voltage_max = 2.2;
|
||||
constexpr float adc_voltage_max = 3.3;
|
||||
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
||||
constexpr int raw_delta = raw_max - raw_min;
|
||||
const range_t<int> x_avg_range { 0, r.width() - 1 };
|
||||
const auto x_avg = x_avg_range.clip((avg_ - raw_min) * r.width() / raw_delta);
|
||||
const range_t<int> x_min_range { 0, x_avg };
|
||||
const auto x_min = x_min_range.clip((min_ - raw_min) * r.width() / raw_delta);
|
||||
const range_t<int> x_max_range { x_avg + 1, r.width() };
|
||||
const auto x_max = x_max_range.clip((max_ - raw_min) * r.width() / raw_delta);
|
||||
|
||||
const Rect r0 {
|
||||
static_cast<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_min - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_min), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@@ -90,7 +96,7 @@ void RSSI::paint(Painter& painter) {
|
||||
|
||||
const Rect r4 {
|
||||
static_cast<ui::Coord>(r.left() + x_max), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - x_max), r.height()
|
||||
static_cast<ui::Dim>(r.width() - x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r4,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_sd_card_status_view.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* SDCardStatusView *****************************************************/
|
||||
|
||||
SDCardStatusView::SDCardStatusView() {
|
||||
add_children({ {
|
||||
&text_status,
|
||||
} });
|
||||
|
||||
on_status(sd_card::status());
|
||||
}
|
||||
|
||||
void SDCardStatusView::on_show() {
|
||||
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) {
|
||||
this->on_status(status);
|
||||
};
|
||||
}
|
||||
|
||||
void SDCardStatusView::on_hide() {
|
||||
sd_card::status_signal -= sd_card_status_signal_token;
|
||||
}
|
||||
|
||||
void SDCardStatusView::on_status(const sd_card::Status status) {
|
||||
std::string msg("??");
|
||||
|
||||
switch(status) {
|
||||
case sd_card::Status::IOError:
|
||||
msg = "IO";
|
||||
break;
|
||||
|
||||
case sd_card::Status::MountError:
|
||||
msg = "MT";
|
||||
break;
|
||||
|
||||
case sd_card::Status::ConnectError:
|
||||
msg = "CN";
|
||||
break;
|
||||
|
||||
case sd_card::Status::NotPresent:
|
||||
msg = "XX";
|
||||
break;
|
||||
|
||||
case sd_card::Status::Present:
|
||||
msg = "OO";
|
||||
break;
|
||||
|
||||
case sd_card::Status::Mounted:
|
||||
msg = "OK";
|
||||
break;
|
||||
|
||||
default:
|
||||
msg = "--";
|
||||
break;
|
||||
}
|
||||
|
||||
text_status.set(msg);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_SD_CARD_STATUS_VIEW_H__
|
||||
#define __UI_SD_CARD_STATUS_VIEW_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "sd_card.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SDCardStatusView : public View {
|
||||
public:
|
||||
SDCardStatusView();
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
private:
|
||||
Text text_status {
|
||||
{ 0 * 8, 0, 2 * 8, 1 * 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
SignalToken sd_card_status_signal_token;
|
||||
|
||||
void on_status(const sd_card::Status status);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_SD_CARD_STATUS_VIEW_H__*/
|
||||
@@ -19,19 +19,14 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_setup.hpp"
|
||||
#include "touch.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <cstring>
|
||||
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
#include "portapack.hpp"
|
||||
using portapack::receiver_model;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -149,15 +144,33 @@ SetFrequencyCorrectionModel SetFrequencyCorrectionView::form_collect() {
|
||||
};
|
||||
}
|
||||
|
||||
SetTouchCalibView::SetTouchCalibView(NavigationView& nav) {
|
||||
add_children({{
|
||||
AntennaBiasSetupView::AntennaBiasSetupView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&text_debugx,
|
||||
&text_debugy,
|
||||
&button_ok
|
||||
}});
|
||||
&text_description_1,
|
||||
&text_description_2,
|
||||
&text_description_3,
|
||||
&text_description_4,
|
||||
&options_bias,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_ok.on_select = [&nav](Button&){ nav.pop(); };
|
||||
options_bias.set_by_value(receiver_model.antenna_bias() ? 1 : 0);
|
||||
options_bias.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_antenna_bias(v);
|
||||
};
|
||||
|
||||
button_done.on_select = [&nav](Button&){ nav.pop(); };
|
||||
}
|
||||
|
||||
void AntennaBiasSetupView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
void SetTouchCalibView::focus() {
|
||||
@@ -419,10 +432,11 @@ void ModInfoView::focus() {
|
||||
}
|
||||
|
||||
SetupMenuView::SetupMenuView(NavigationView& nav) {
|
||||
add_items<6>({ {
|
||||
add_items<7>({ {
|
||||
{ "SD card modules", ui::Color::white(), [&nav](){ nav.push(new ModInfoView { nav }); } },
|
||||
{ "Date/Time", ui::Color::white(), [&nav](){ nav.push(new SetDateTimeView { nav }); } },
|
||||
{ "Frequency correction", ui::Color::white(), [&nav](){ nav.push(new SetFrequencyCorrectionView { nav }); } },
|
||||
{ "Frequency correction", ui::Color::white(), [&nav](){ nav.push<SetFrequencyCorrectionView>(); } },
|
||||
{ "Antenna Bias Voltage", [&nav](){ nav.push<AntennaBiasSetupView>(); } },
|
||||
{ "Touch screen", ui::Color::white(), [&nav](){ nav.push(new SetTouchCalibView { nav }); } },
|
||||
{ "Play dead", ui::Color::red(), [&nav](){ nav.push(new SetPlayDeadView { nav }); } },
|
||||
{ "UI", ui::Color::white(), [&nav](){ nav.push(new SetUIView { nav }); } },
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#ifndef __UI_SETUP_H__
|
||||
#define __UI_SETUP_H__
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -207,129 +205,50 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class SetUIView : public View {
|
||||
class AntennaBiasSetupView : public View {
|
||||
public:
|
||||
SetUIView(NavigationView& nav);
|
||||
AntennaBiasSetupView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
|
||||
private:
|
||||
Checkbox checkbox_showsplash {
|
||||
{ 3 * 8, 2 * 16},
|
||||
"Show splash"
|
||||
};
|
||||
|
||||
Checkbox checkbox_bloff {
|
||||
{ 3 * 8, 4 * 16},
|
||||
"Backlight off after:"
|
||||
Text text_title {
|
||||
{ 5 * 8, 3 * 16, 20 * 8, 16 },
|
||||
"Antenna Bias Voltage"
|
||||
};
|
||||
|
||||
OptionsField options_bloff {
|
||||
{ 10 * 8, 5 * 16 + 4 },
|
||||
10,
|
||||
Text text_description_1 {
|
||||
{ 24, 6 * 16, 24 * 8, 16 },
|
||||
"CAUTION: Ensure that all"
|
||||
};
|
||||
|
||||
Text text_description_2 {
|
||||
{ 28, 7 * 16, 23 * 8, 16 },
|
||||
"devices attached to the"
|
||||
};
|
||||
|
||||
Text text_description_3 {
|
||||
{ 8, 8 * 16, 28 * 8, 16 },
|
||||
"antenna connector can accept"
|
||||
};
|
||||
|
||||
Text text_description_4 {
|
||||
{ 68, 9 * 16, 13 * 8, 16 },
|
||||
"a DC voltage!"
|
||||
};
|
||||
|
||||
OptionsField options_bias {
|
||||
{ 100, 12 * 16 },
|
||||
5,
|
||||
{
|
||||
{ "5 seconds ", 0 },
|
||||
{ "15 seconds", 1 },
|
||||
{ "1 minute ", 2 },
|
||||
{ "5 minutes ", 3 },
|
||||
{ "10 minutes", 4 }
|
||||
{ " Off ", 0 },
|
||||
{ " On ", 1 },
|
||||
}
|
||||
};
|
||||
|
||||
Button button_ok {
|
||||
{ 4 * 8, 272, 64, 24 },
|
||||
"Ok"
|
||||
};
|
||||
};
|
||||
|
||||
class SetPlayDeadView : public View {
|
||||
public:
|
||||
SetPlayDeadView(NavigationView& nav);
|
||||
void focus() override;
|
||||
private:
|
||||
bool entermode = false;
|
||||
uint32_t sequence = 0;
|
||||
uint8_t keycount, key_code;
|
||||
char sequence_txt[11];
|
||||
|
||||
Text text_sequence {
|
||||
{ 64, 32, 14 * 8, 16 },
|
||||
"Enter sequence",
|
||||
};
|
||||
|
||||
Button button_enter {
|
||||
{ 16, 192, 96, 24 },
|
||||
"Enter"
|
||||
};
|
||||
Button button_cancel {
|
||||
{ 128, 192, 96, 24 },
|
||||
"Cancel"
|
||||
};
|
||||
};
|
||||
|
||||
class ModInfoView : public View {
|
||||
public:
|
||||
ModInfoView(NavigationView& nav);
|
||||
void focus() override;
|
||||
void on_show() override;
|
||||
|
||||
private:
|
||||
void update_infos(uint8_t modn);
|
||||
|
||||
typedef struct moduleinfo_t{
|
||||
char filename[9];
|
||||
uint16_t version;
|
||||
uint32_t size;
|
||||
char md5[16];
|
||||
char name[16];
|
||||
char description[214];
|
||||
} moduleinfo_t;
|
||||
|
||||
moduleinfo_t module_list[8]; // 8 max for now
|
||||
|
||||
Text text_modcount {
|
||||
{ 2 * 8, 1 * 16, 18 * 8, 16 },
|
||||
"Searching..."
|
||||
};
|
||||
|
||||
OptionsField option_modules {
|
||||
{ 2 * 8, 2 * 16 },
|
||||
24,
|
||||
{ { "-", 0 }
|
||||
}
|
||||
};
|
||||
|
||||
Text text_name {
|
||||
{ 2 * 8, 4 * 16, 5 * 8, 16 },
|
||||
"Name:"
|
||||
};
|
||||
Text text_namestr {
|
||||
{ 8 * 8, 4 * 16, 16 * 8, 16 },
|
||||
"..."
|
||||
};
|
||||
Text text_size {
|
||||
{ 2 * 8, 5 * 16, 5 * 8, 16 },
|
||||
"Size:"
|
||||
};
|
||||
Text text_sizestr {
|
||||
{ 8 * 8, 5 * 16, 16 * 8, 16 },
|
||||
"..."
|
||||
};
|
||||
Text text_md5 {
|
||||
{ 2 * 8, 6 * 16, 4 * 8, 16 },
|
||||
"MD5:"
|
||||
};
|
||||
Text text_md5_a {
|
||||
{ 7 * 8, 6 * 16, 16 * 8, 16 },
|
||||
"..."
|
||||
};
|
||||
Text text_md5_b {
|
||||
{ 7 * 8, 7 * 16, 16 * 8, 16 },
|
||||
"..."
|
||||
};
|
||||
|
||||
Button button_ok {
|
||||
{ 4 * 8, 272, 64, 24 },
|
||||
"Ok"
|
||||
Button button_done {
|
||||
{ 72, 15 * 16, 96, 24 },
|
||||
"Done"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,279 @@
|
||||
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
namespace ui {
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "spectrum_color_lut.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
|
||||
namespace ui {
|
||||
namespace spectrum {
|
||||
|
||||
/* FrequencyScale ********************************************************/
|
||||
|
||||
void FrequencyScale::on_show() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void FrequencyScale::set_spectrum_sampling_rate(const int new_sampling_rate) {
|
||||
if( (spectrum_sampling_rate != new_sampling_rate) ) {
|
||||
spectrum_sampling_rate = new_sampling_rate;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyScale::set_channel_filter(
|
||||
const int pass_frequency,
|
||||
const int stop_frequency
|
||||
) {
|
||||
if( (channel_filter_pass_frequency != pass_frequency) ||
|
||||
(channel_filter_stop_frequency != stop_frequency) ) {
|
||||
channel_filter_pass_frequency = pass_frequency;
|
||||
channel_filter_stop_frequency = stop_frequency;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyScale::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
clear_background(painter, r);
|
||||
|
||||
if( !spectrum_sampling_rate ) {
|
||||
// Can't draw without non-zero scale.
|
||||
return;
|
||||
}
|
||||
|
||||
draw_filter_ranges(painter, r);
|
||||
draw_frequency_ticks(painter, r);
|
||||
}
|
||||
|
||||
void FrequencyScale::clear() {
|
||||
spectrum_sampling_rate = 0;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void FrequencyScale::clear_background(Painter& painter, const Rect r) {
|
||||
painter.fill_rectangle(r, Color::black());
|
||||
}
|
||||
|
||||
void FrequencyScale::draw_frequency_ticks(Painter& painter, const Rect r) {
|
||||
const auto x_center = r.width() / 2;
|
||||
|
||||
const Rect tick { r.left() + x_center, r.top(), 1, r.height() };
|
||||
painter.fill_rectangle(tick, Color::white());
|
||||
|
||||
constexpr int tick_count_max = 4;
|
||||
float rough_tick_interval = float(spectrum_sampling_rate) / tick_count_max;
|
||||
int magnitude = 1;
|
||||
int magnitude_n = 0;
|
||||
while(rough_tick_interval >= 10.0f) {
|
||||
rough_tick_interval /= 10;
|
||||
magnitude *= 10;
|
||||
magnitude_n += 1;
|
||||
}
|
||||
const int tick_interval = std::ceil(rough_tick_interval);
|
||||
|
||||
auto tick_offset = tick_interval;
|
||||
while((tick_offset * magnitude) < spectrum_sampling_rate / 2) {
|
||||
const Dim pixel_offset = tick_offset * magnitude * spectrum_bins / spectrum_sampling_rate;
|
||||
|
||||
const std::string zero_pad =
|
||||
((magnitude_n % 3) == 0) ? "" :
|
||||
((magnitude_n % 3) == 1) ? "0" : "00";
|
||||
const std::string unit =
|
||||
(magnitude_n >= 6) ? "M" :
|
||||
(magnitude_n >= 3) ? "k" : "";
|
||||
const std::string label = to_string_dec_uint(tick_offset) + zero_pad + unit;
|
||||
|
||||
const Coord offset_low = r.left() + x_center - pixel_offset;
|
||||
const Rect tick_low { offset_low, r.top(), 1, r.height() };
|
||||
painter.fill_rectangle(tick_low, Color::white());
|
||||
painter.draw_string({ offset_low + 2, r.top() }, style(), label );
|
||||
|
||||
const Coord offset_high = r.left() + x_center + pixel_offset;
|
||||
const Rect tick_high { offset_high, r.top(), 1, r.height() };
|
||||
painter.fill_rectangle(tick_high, Color::white());
|
||||
painter.draw_string({ offset_high + 2, r.top() }, style(), label );
|
||||
|
||||
tick_offset += tick_interval;
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyScale::draw_filter_ranges(Painter& painter, const Rect r) {
|
||||
if( channel_filter_pass_frequency ) {
|
||||
const auto x_center = r.width() / 2;
|
||||
|
||||
const auto pass_offset = channel_filter_pass_frequency * spectrum_bins / spectrum_sampling_rate;
|
||||
const auto stop_offset = channel_filter_stop_frequency * spectrum_bins / spectrum_sampling_rate;
|
||||
|
||||
const auto pass_x_lo = x_center - pass_offset;
|
||||
const auto pass_x_hi = x_center + pass_offset;
|
||||
|
||||
if( channel_filter_stop_frequency ) {
|
||||
const auto stop_x_lo = x_center - stop_offset;
|
||||
const auto stop_x_hi = x_center + stop_offset;
|
||||
|
||||
const Rect r_stop_lo {
|
||||
r.left() + stop_x_lo, r.bottom() - filter_band_height,
|
||||
pass_x_lo - stop_x_lo, filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_lo,
|
||||
Color::yellow()
|
||||
);
|
||||
|
||||
const Rect r_stop_hi {
|
||||
r.left() + pass_x_hi, r.bottom() - filter_band_height,
|
||||
stop_x_hi - pass_x_hi, filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_hi,
|
||||
Color::yellow()
|
||||
);
|
||||
}
|
||||
|
||||
const Rect r_pass {
|
||||
r.left() + pass_x_lo, r.bottom() - filter_band_height,
|
||||
pass_x_hi - pass_x_lo, filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_pass,
|
||||
Color::green()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* WaterfallView *********************************************************/
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
clear();
|
||||
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
}
|
||||
|
||||
void WaterfallView::on_hide() {
|
||||
/* TODO: Clear region to eliminate brief flash of content at un-shifted
|
||||
* position?
|
||||
*/
|
||||
display.scroll_disable();
|
||||
}
|
||||
|
||||
void WaterfallView::paint(Painter& painter) {
|
||||
// Do nothing.
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallView::on_channel_spectrum(
|
||||
const ChannelSpectrum& spectrum
|
||||
) {
|
||||
/* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */
|
||||
|
||||
std::array<Color, 240> pixel_row;
|
||||
for(size_t i=0; i<120; i++) {
|
||||
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[256 - 120 + i]];
|
||||
pixel_row[i] = pixel_color;
|
||||
}
|
||||
|
||||
for(size_t i=120; i<240; i++) {
|
||||
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[i - 120]];
|
||||
pixel_row[i] = pixel_color;
|
||||
}
|
||||
|
||||
const auto draw_y = display.scroll(1);
|
||||
|
||||
display.draw_pixels(
|
||||
{ { 0, draw_y }, { pixel_row.size(), 1 } },
|
||||
pixel_row
|
||||
);
|
||||
}
|
||||
|
||||
void WaterfallView::clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black()
|
||||
);
|
||||
}
|
||||
|
||||
/* WaterfallWidget *******************************************************/
|
||||
|
||||
WaterfallWidget::WaterfallWidget() {
|
||||
add_children({
|
||||
&waterfall_view,
|
||||
&frequency_scale,
|
||||
});
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_show() {
|
||||
EventDispatcher::message_map().register_handler(Message::ID::ChannelSpectrumConfig,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||
this->fifo = message.fifo;
|
||||
}
|
||||
);
|
||||
EventDispatcher::message_map().register_handler(Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if( this->fifo ) {
|
||||
ChannelSpectrum channel_spectrum;
|
||||
while( fifo->out(channel_spectrum) ) {
|
||||
this->on_channel_spectrum(channel_spectrum);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
SpectrumStreamingConfigMessage {
|
||||
SpectrumStreamingConfigMessage::Mode::Running
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_hide() {
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
SpectrumStreamingConfigMessage {
|
||||
SpectrumStreamingConfigMessage::Mode::Stopped
|
||||
}
|
||||
);
|
||||
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync);
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::ChannelSpectrumConfig);
|
||||
}
|
||||
|
||||
void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
constexpr Dim scale_height = 20;
|
||||
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height });
|
||||
waterfall_view.set_parent_rect({
|
||||
0, scale_height,
|
||||
new_parent_rect.width(),
|
||||
new_parent_rect.height() - scale_height
|
||||
});
|
||||
}
|
||||
|
||||
void WaterfallWidget::paint(Painter& painter) {
|
||||
// TODO:
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_view.on_channel_spectrum(spectrum);
|
||||
frequency_scale.set_spectrum_sampling_rate(spectrum.sampling_rate);
|
||||
frequency_scale.set_channel_filter(
|
||||
spectrum.channel_filter_pass_frequency,
|
||||
spectrum.channel_filter_stop_frequency
|
||||
);
|
||||
}
|
||||
|
||||
} /* namespace spectrum */
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -24,274 +24,69 @@
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace ui {
|
||||
namespace spectrum {
|
||||
|
||||
class FrequencyScale : public Widget {
|
||||
public:
|
||||
void on_show() override {
|
||||
clear();
|
||||
}
|
||||
void on_show() override;
|
||||
|
||||
void set_spectrum_sampling_rate(const uint32_t new_sampling_rate, const size_t new_spectrum_bins) {
|
||||
if( (spectrum_sampling_rate != new_sampling_rate) ||
|
||||
(spectrum_bins != new_spectrum_bins) ) {
|
||||
spectrum_sampling_rate = new_sampling_rate;
|
||||
spectrum_bins = new_spectrum_bins;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
void set_spectrum_sampling_rate(const int new_sampling_rate);
|
||||
void set_channel_filter(const int pass_frequency, const int stop_frequency);
|
||||
|
||||
void set_channel_filter(
|
||||
const uint32_t pass_frequency,
|
||||
const uint32_t stop_frequency
|
||||
) {
|
||||
if( (channel_filter_pass_frequency != pass_frequency) ||
|
||||
(channel_filter_stop_frequency != stop_frequency) ) {
|
||||
channel_filter_pass_frequency = pass_frequency;
|
||||
channel_filter_stop_frequency = stop_frequency;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override {
|
||||
const auto r = screen_rect();
|
||||
|
||||
clear_background(painter, r);
|
||||
|
||||
if( !spectrum_sampling_rate || !spectrum_bins ) {
|
||||
// Can't draw without non-zero scale.
|
||||
return;
|
||||
}
|
||||
|
||||
draw_filter_ranges(painter, r);
|
||||
draw_frequency_ticks(painter, r);
|
||||
}
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
static constexpr Dim filter_band_height = 4;
|
||||
static constexpr int filter_band_height = 4;
|
||||
|
||||
uint32_t spectrum_sampling_rate { 0 };
|
||||
size_t spectrum_bins { 0 };
|
||||
uint32_t channel_filter_pass_frequency { 0 };
|
||||
uint32_t channel_filter_stop_frequency { 0 };
|
||||
int spectrum_sampling_rate { 0 };
|
||||
const int spectrum_bins = std::tuple_size<decltype(ChannelSpectrum::db)>::value;
|
||||
int channel_filter_pass_frequency { 0 };
|
||||
int channel_filter_stop_frequency { 0 };
|
||||
|
||||
void clear() {
|
||||
spectrum_sampling_rate = 0;
|
||||
spectrum_bins = 0;
|
||||
set_dirty();
|
||||
}
|
||||
void clear();
|
||||
void clear_background(Painter& painter, const Rect r);
|
||||
|
||||
void clear_background(Painter& painter, const Rect r) {
|
||||
painter.fill_rectangle(r, Color::black());
|
||||
}
|
||||
|
||||
void draw_frequency_ticks(Painter& painter, const Rect r) {
|
||||
const auto x_center = r.width() / 2;
|
||||
|
||||
const Rect tick {
|
||||
static_cast<Coord>(r.left() + x_center), r.top(),
|
||||
1, r.height()
|
||||
};
|
||||
painter.fill_rectangle(tick, Color::white());
|
||||
|
||||
constexpr uint32_t tick_count_max = 4;
|
||||
float rough_tick_interval = float(spectrum_sampling_rate) / tick_count_max;
|
||||
size_t magnitude = 1;
|
||||
size_t magnitude_n = 0;
|
||||
while(rough_tick_interval >= 10.0f) {
|
||||
rough_tick_interval /= 10;
|
||||
magnitude *= 10;
|
||||
magnitude_n += 1;
|
||||
}
|
||||
const size_t tick_interval = std::ceil(rough_tick_interval);
|
||||
|
||||
size_t tick_offset = tick_interval;
|
||||
while((tick_offset * magnitude) < spectrum_sampling_rate / 2) {
|
||||
const Dim pixel_offset = tick_offset * magnitude * spectrum_bins / spectrum_sampling_rate;
|
||||
|
||||
const std::string zero_pad =
|
||||
((magnitude_n % 3) == 0) ? "" :
|
||||
((magnitude_n % 3) == 1) ? "0" : "00";
|
||||
const std::string unit =
|
||||
(magnitude_n >= 6) ? "M" :
|
||||
(magnitude_n >= 3) ? "k" : "";
|
||||
const std::string label = to_string_dec_uint(tick_offset) + zero_pad + unit;
|
||||
|
||||
const Coord offset_low = static_cast<Coord>(r.left() + x_center - pixel_offset);
|
||||
const Rect tick_low { offset_low, r.top(), 1, r.height() };
|
||||
painter.fill_rectangle(tick_low, Color::white());
|
||||
painter.draw_string({ static_cast<Coord>(offset_low + 2), r.top() }, style(), label );
|
||||
|
||||
const Coord offset_high = static_cast<Coord>(r.left() + x_center + pixel_offset);
|
||||
const Rect tick_high { offset_high, r.top(), 1, r.height() };
|
||||
painter.fill_rectangle(tick_high, Color::white());
|
||||
painter.draw_string({ static_cast<Coord>(offset_high + 2), r.top() }, style(), label );
|
||||
|
||||
tick_offset += tick_interval;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_filter_ranges(Painter& painter, const Rect r) {
|
||||
if( channel_filter_pass_frequency ) {
|
||||
const auto x_center = r.width() / 2;
|
||||
|
||||
const auto pass_offset = channel_filter_pass_frequency * spectrum_bins / spectrum_sampling_rate;
|
||||
const auto stop_offset = channel_filter_stop_frequency * spectrum_bins / spectrum_sampling_rate;
|
||||
|
||||
const auto pass_x_lo = x_center - pass_offset;
|
||||
const auto pass_x_hi = x_center + pass_offset;
|
||||
|
||||
if( channel_filter_stop_frequency ) {
|
||||
const auto stop_x_lo = x_center - stop_offset;
|
||||
const auto stop_x_hi = x_center + stop_offset;
|
||||
|
||||
const Rect r_stop_lo {
|
||||
static_cast<Coord>(r.left() + stop_x_lo), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(pass_x_lo - stop_x_lo), filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_lo,
|
||||
Color::yellow()
|
||||
);
|
||||
|
||||
const Rect r_stop_hi {
|
||||
static_cast<Coord>(r.left() + pass_x_hi), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(stop_x_hi - pass_x_hi), filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_hi,
|
||||
Color::yellow()
|
||||
);
|
||||
}
|
||||
|
||||
const Rect r_pass {
|
||||
static_cast<Coord>(r.left() + pass_x_lo), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(pass_x_hi - pass_x_lo), filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_pass,
|
||||
Color::green()
|
||||
);
|
||||
}
|
||||
}
|
||||
void draw_frequency_ticks(Painter& painter, const Rect r);
|
||||
void draw_filter_ranges(Painter& painter, const Rect r);
|
||||
};
|
||||
|
||||
class WaterfallView : public Widget {
|
||||
public:
|
||||
void on_show() override {
|
||||
clear();
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
}
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
void on_hide() override {
|
||||
/* TODO: Clear region to eliminate brief flash of content at un-shifted
|
||||
* position?
|
||||
*/
|
||||
display.scroll_disable();
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override {
|
||||
// Do nothing.
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void on_channel_spectrum(
|
||||
const ChannelSpectrum& spectrum
|
||||
) {
|
||||
/* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */
|
||||
|
||||
std::array<Color, 240> pixel_row;
|
||||
for(size_t i=0; i<120; i++) {
|
||||
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[256 - 120 + i]];
|
||||
pixel_row[i] = pixel_color;
|
||||
}
|
||||
|
||||
for(size_t i=120; i<240; i++) {
|
||||
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[i - 120]];
|
||||
pixel_row[i] = pixel_color;
|
||||
}
|
||||
|
||||
const auto draw_y = display.scroll(1);
|
||||
|
||||
display.draw_pixels(
|
||||
{ { 0, draw_y }, { pixel_row.size(), 1 } },
|
||||
pixel_row
|
||||
);
|
||||
}
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
|
||||
private:
|
||||
void clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black()
|
||||
);
|
||||
}
|
||||
void clear();
|
||||
};
|
||||
|
||||
class WaterfallWidget : public View {
|
||||
public:
|
||||
WaterfallWidget() {
|
||||
add_children({
|
||||
&waterfall_view,
|
||||
&frequency_scale,
|
||||
});
|
||||
}
|
||||
WaterfallWidget();
|
||||
|
||||
void on_show() override {
|
||||
context().message_map().register_handler(Message::ID::ChannelSpectrum,
|
||||
[this](const Message* const p) {
|
||||
this->on_channel_spectrum(reinterpret_cast<const ChannelSpectrumMessage*>(p)->spectrum);
|
||||
}
|
||||
);
|
||||
}
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void on_hide() override {
|
||||
context().message_map().unregister_handler(Message::ID::ChannelSpectrum);
|
||||
}
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override {
|
||||
constexpr Dim scale_height = 20;
|
||||
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height });
|
||||
waterfall_view.set_parent_rect({
|
||||
0, scale_height,
|
||||
new_parent_rect.width(),
|
||||
static_cast<Dim>(new_parent_rect.height() - scale_height)
|
||||
});
|
||||
}
|
||||
|
||||
void paint(Painter& painter) override {
|
||||
// TODO:
|
||||
(void)painter;
|
||||
}
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
WaterfallView waterfall_view;
|
||||
FrequencyScale frequency_scale;
|
||||
ChannelSpectrumFIFO* fifo;
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_view.on_channel_spectrum(spectrum);
|
||||
frequency_scale.set_spectrum_sampling_rate(spectrum.sampling_rate, spectrum.db_count);
|
||||
frequency_scale.set_channel_filter(
|
||||
spectrum.channel_filter_pass_frequency,
|
||||
spectrum.channel_filter_stop_frequency
|
||||
);
|
||||
}
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
};
|
||||
|
||||
} /* namespace spectrum */
|
||||
|
||||
Reference in New Issue
Block a user