mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 18:43:01 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 322b06d670 | |||
| db187301ab | |||
| 84cb32ee14 | |||
| 61bd696b69 | |||
| db65ae192a | |||
| 7553dafffc | |||
| cfdd1a4c22 | |||
| d4342e01a4 | |||
| 4342f5c0ee | |||
| bd781ce37b | |||
| c2e05dea48 | |||
| de3212cba5 | |||
| 9aff3f4121 | |||
| 6b6a00d511 | |||
| 806219f46e | |||
| a153cbf30b | |||
| 584af02dba | |||
| f344a1ca40 | |||
| 511675c9a1 | |||
| 7bbd608c90 | |||
| f941ae871a | |||
| eff214cc79 | |||
| 319ed3e7b4 | |||
| 288f6bd517 |
@@ -28,7 +28,7 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/ABDO6H) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ set(CPPSRC
|
||||
clock_manager.cpp
|
||||
core_control.cpp
|
||||
database.cpp
|
||||
gradient.cpp
|
||||
rfm69.cpp
|
||||
event_m0.cpp
|
||||
file_reader.cpp
|
||||
|
||||
@@ -114,6 +114,26 @@ WFMOptionsView::WFMOptionsView(
|
||||
};
|
||||
}
|
||||
|
||||
/* WFMAMAptOptionsView *******************************************************/
|
||||
|
||||
WFMAMAptOptionsView::WFMAMAptOptionsView(
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_by_value(receiver_model.wfmam_configuration());
|
||||
options_config.on_change = [this](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_wfmam_configuration(n);
|
||||
};
|
||||
}
|
||||
|
||||
/* AMFMAptOptionsView *********************************************************/
|
||||
|
||||
AMFMAptOptionsView::AMFMAptOptionsView(
|
||||
@@ -415,6 +435,12 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
widget = std::make_unique<WFMAMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(true);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
widget = std::make_unique<AMFMAptOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
@@ -462,7 +488,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it.
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_am_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
@@ -501,7 +530,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
sampling_rate = 48000;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -74,7 +74,7 @@ class AMFMAptOptionsView : public View {
|
||||
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
6, // Max option length chars
|
||||
17, // Max option length chars "USB+FM(Wefax Apt)"
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
|
||||
}};
|
||||
@@ -132,6 +132,23 @@ class WFMOptionsView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
class WFMAMAptOptionsView : public View {
|
||||
public:
|
||||
WFMAMAptOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
16, // Max option char length "80khz (NOAA Apt)" example.
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
public:
|
||||
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
@@ -215,7 +232,7 @@ class AnalogAudioView : public View {
|
||||
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
|
||||
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
//
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio",
|
||||
app_settings::Mode::RX,
|
||||
@@ -261,7 +278,8 @@ class AnalogAudioView : public View {
|
||||
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
|
||||
}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ble_rx_app.hpp"
|
||||
#include "ble_rx_app.hpp"
|
||||
#include "ui_modemsetup.hpp"
|
||||
|
||||
|
||||
@@ -91,10 +91,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
stop();
|
||||
|
||||
if (!reader->open(u"/WAV/" + file_list[id].native())) {
|
||||
@@ -108,7 +104,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
// button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
uint32_t sample_rate = reader->sample_rate();
|
||||
|
||||
tone_key_index = options_tone_key.selected_index();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
@@ -155,6 +153,23 @@ void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void SoundBoardView::update_config() {
|
||||
// NB: this were called by the on_bandwidth_changed() callback,
|
||||
// so other val would be updated too when bw changed. currently it's safe but be careful.
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
bits_per_sample,
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
}
|
||||
|
||||
void SoundBoardView::on_select_entry() {
|
||||
tx_view.focus();
|
||||
}
|
||||
@@ -289,6 +304,10 @@ SoundBoardView::SoundBoardView(
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_bandwidth_changed = [this]() {
|
||||
update_config();
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
start_tx(menu_view.highlighted_index());
|
||||
};
|
||||
|
||||
@@ -70,6 +70,8 @@ class SoundBoardView : public View {
|
||||
uint32_t playing_id{};
|
||||
uint32_t page = 1;
|
||||
uint32_t c_page = 1;
|
||||
uint32_t tone_key_index = 1;
|
||||
uint8_t bits_per_sample = 1;
|
||||
|
||||
std::vector<std::filesystem::path> file_list{};
|
||||
|
||||
@@ -90,6 +92,7 @@ class SoundBoardView : public View {
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
void refresh_list();
|
||||
void on_select_entry();
|
||||
void update_config();
|
||||
|
||||
Labels labels{
|
||||
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
|
||||
|
||||
static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
||||
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
|
||||
}
|
||||
@@ -70,18 +72,34 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
||||
|
||||
entry_string +=
|
||||
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
|
||||
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
|
||||
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
|
||||
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
|
||||
to_string_dec_uint(entry.age, 4);
|
||||
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
|
||||
|
||||
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
|
||||
// It is generally accepted that for every thousand feet of altitude,
|
||||
// true airspeed is approximately 2% higher than indicated airspeed.
|
||||
// Since the application CPU has no floating point unit, we avoid floating point here
|
||||
// tas = entry.velo.speed + (float)entry.pos.altitude / 1000.0 * 0.02 * entry.velo.speed;
|
||||
unsigned int tas = entry.velo.speed + entry.pos.altitude * 2 * entry.velo.speed / 100000;
|
||||
|
||||
entry_string +=
|
||||
to_string_dec_uint(tas, 4) + '*' +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 3);
|
||||
} else {
|
||||
entry_string +=
|
||||
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
|
||||
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4);
|
||||
}
|
||||
|
||||
entry_string += " " +
|
||||
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
|
||||
to_string_dec_uint(entry.age, 4);
|
||||
|
||||
painter.draw_string(
|
||||
target_rect.location(),
|
||||
style,
|
||||
entry_string);
|
||||
|
||||
if (entry.pos.valid)
|
||||
if (entry.pos.pos_valid)
|
||||
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
|
||||
bitmap_target, target_color, style.background);
|
||||
}
|
||||
@@ -93,22 +111,31 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
||||
log_line.reserve(100);
|
||||
|
||||
log_line = log_entry.raw_data;
|
||||
log_line += "ICAO:" + log_entry.icao;
|
||||
log_line += " ICAO:" + log_entry.icao;
|
||||
|
||||
if (log_entry.sqwk)
|
||||
log_line += " Squawk:" + to_string_dec_uint(log_entry.sqwk, 4, '0');
|
||||
|
||||
if (!log_entry.callsign.empty())
|
||||
log_line += " " + log_entry.callsign;
|
||||
|
||||
if (log_entry.pos.valid)
|
||||
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
|
||||
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
|
||||
if (log_entry.pos.alt_valid)
|
||||
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude);
|
||||
|
||||
if (log_entry.pos.pos_valid)
|
||||
log_line += " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
|
||||
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
|
||||
|
||||
if (log_entry.vel.valid)
|
||||
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
|
||||
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
|
||||
" Spd: " + to_string_dec_int(log_entry.vel.speed);
|
||||
speed_type_msg[log_entry.vel.type] +
|
||||
to_string_dec_int(log_entry.vel.speed) +
|
||||
" Vrate:" + to_string_dec_int(log_entry.vel.v_rate);
|
||||
|
||||
if (log_entry.sil != 0)
|
||||
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
|
||||
|
||||
log_file.write_entry(log_line);
|
||||
}
|
||||
|
||||
@@ -258,7 +285,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
get_map_tag(entry_),
|
||||
entry_.pos.altitude,
|
||||
GeoPos::alt_unit::FEET,
|
||||
GeoPos::spd_unit::MPH,
|
||||
GeoPos::spd_unit::HIDDEN,
|
||||
entry_.pos.latitude,
|
||||
entry_.pos.longitude,
|
||||
entry_.velo.heading);
|
||||
@@ -356,11 +383,12 @@ void ADSBRxDetailsView::refresh_ui() {
|
||||
text_callsign.set(entry_.callsign);
|
||||
text_infos.set(entry_.info_string);
|
||||
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
|
||||
std::string str_sqw = (entry_.sqwk > 0) ? " Sqw:" + to_string_dec_uint(entry_.sqwk) : "";
|
||||
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
|
||||
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
|
||||
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
|
||||
speed_type_msg[entry_.velo.type] + to_string_dec_int(entry_.velo.speed) + str_sil + str_sqw);
|
||||
else
|
||||
text_info2.set(str_sil);
|
||||
text_info2.set(str_sil + str_sqw);
|
||||
|
||||
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
|
||||
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
|
||||
@@ -421,14 +449,22 @@ void ADSBRxView::focus() {
|
||||
|
||||
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
auto frame = message->frame;
|
||||
uint32_t ICAO_address = frame.get_ICAO_address();
|
||||
status_frame.toggle();
|
||||
|
||||
// Bad frame, skip it.
|
||||
if (!frame.check_CRC() || ICAO_address == 0)
|
||||
return;
|
||||
uint32_t ICAO_address;
|
||||
uint32_t crc = frame.check_CRC();
|
||||
|
||||
if (crc != 0) {
|
||||
if (find(recent, crc) != recent.end())
|
||||
ICAO_address = crc;
|
||||
else
|
||||
return; // Bad frame, skip it.
|
||||
} else {
|
||||
ICAO_address = frame.get_ICAO_address();
|
||||
if (ICAO_address == 0)
|
||||
return; // Bad frame, skip it.
|
||||
}
|
||||
|
||||
ADSBLogEntry log_entry;
|
||||
status_good_frame.toggle();
|
||||
|
||||
rtc::RTC datetime;
|
||||
@@ -440,42 +476,68 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
entry.inc_hit();
|
||||
entry.reset_age();
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
|
||||
// Store smoothed amplitude on updates.
|
||||
entry.amp = entry.hits == 0
|
||||
? message->amp
|
||||
: ((entry.amp * 15) + message->amp) >> 4;
|
||||
|
||||
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
|
||||
uint8_t df = frame.get_DF();
|
||||
|
||||
if (df == 11) // do not log DF11, because messages arrive too frequently
|
||||
return;
|
||||
|
||||
ADSBLogEntry log_entry;
|
||||
uint8_t* raw_data = frame.get_raw_data();
|
||||
|
||||
if (df & 0x10) // 112 bits
|
||||
log_entry.raw_data = to_string_hex_array(raw_data, 14);
|
||||
else { // 56 bits
|
||||
log_entry.raw_data = to_string_hex_array(raw_data, 7);
|
||||
log_entry.raw_data.append(14, ' ');
|
||||
}
|
||||
|
||||
log_entry.icao = entry.icao_str;
|
||||
|
||||
if (frame.get_DF() == DF_ADSB) {
|
||||
// 17: // Extended squitter
|
||||
// 18: // Extended squitter/non-transponder
|
||||
if (df == DF_ADSB) {
|
||||
uint8_t msg_type = frame.get_msg_type();
|
||||
uint8_t msg_sub = frame.get_msg_sub();
|
||||
uint8_t* raw_data = frame.get_raw_data();
|
||||
|
||||
// 4: // surveillance, altitude reply
|
||||
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
|
||||
// transmitted when horizontal position information is not available but altitude information is available
|
||||
if (msg_type == 0) {
|
||||
// Q-bit must be present
|
||||
if (raw_data[5] & 1) {
|
||||
int altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
|
||||
|
||||
log_entry.pos.altitude = entry.pos.altitude = altitude;
|
||||
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
|
||||
}
|
||||
}
|
||||
// 1-4: Aircraft identification
|
||||
else if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
|
||||
entry.set_callsign(decode_frame_id(frame));
|
||||
log_entry.callsign = entry.callsign;
|
||||
}
|
||||
|
||||
// 9:
|
||||
// 18: // Extended squitter/non-transponder
|
||||
// 21: // Comm-B, identity reply
|
||||
// 20: // Comm-B, altitude reply
|
||||
// 9-18: Airborne position (w/Baro Altitude)
|
||||
// 20-22: Airborne position (w/GNSS Height)
|
||||
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
|
||||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
|
||||
entry.set_frame_pos(frame, raw_data[6] & 4);
|
||||
log_entry.pos = entry.pos;
|
||||
|
||||
if (entry.pos.valid) {
|
||||
if (entry.pos.pos_valid) {
|
||||
std::string str_info =
|
||||
"Alt:" + to_string_dec_int(entry.pos.altitude) +
|
||||
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
|
||||
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
|
||||
entry.set_info_string(std::move(str_info));
|
||||
}
|
||||
|
||||
// 19: Airborne velocities
|
||||
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
|
||||
entry.set_frame_velo(frame);
|
||||
log_entry.vel = entry.velo;
|
||||
@@ -485,11 +547,59 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
}
|
||||
}
|
||||
|
||||
logger->log(log_entry);
|
||||
// 4: // surveillance, altitude reply
|
||||
// 20: // Comm-B, altitude reply
|
||||
// 21: // Comm-B, identity reply
|
||||
if (df == 0 || df == 4 || df == 20) { // Decode the 13 bit AC altitude field
|
||||
uint8_t m_bit = raw_data[3] & (1 << 6);
|
||||
uint8_t q_bit = raw_data[3] & (1 << 4);
|
||||
int altitude = 0;
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
if (!m_bit) { // units -> FEET
|
||||
if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M
|
||||
int n = ((raw_data[2] & 31) << 6) |
|
||||
((raw_data[3] & 0x80) >> 2) |
|
||||
((raw_data[3] & 0x20) >> 1) |
|
||||
(raw_data[3] & 15);
|
||||
|
||||
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
|
||||
altitude = 25 * n - 1000;
|
||||
if (altitude < 0)
|
||||
altitude = 0;
|
||||
} // else N is an 11 bit Gillham coded altitude
|
||||
}
|
||||
|
||||
log_entry.pos.altitude = entry.pos.altitude = altitude;
|
||||
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
|
||||
}
|
||||
|
||||
if (df == 5 || df == 21 ||
|
||||
(df == 17 && frame.get_msg_type() == 28 && frame.get_msg_sub() == 1)) { // Decode the squawk code
|
||||
uint8_t* s = (df == 17) ? raw_data + 5 : raw_data + 2; // calc start of the code
|
||||
uint16_t sqwk{0};
|
||||
|
||||
sqwk = ((s[1] & 0x80) >> 5) | ((s[0] & 0x02) >> 0) | ((s[0] & 0x08) >> 3); // A
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[1] & 0x02) << 1) | ((s[1] & 0x08) >> 2) | ((s[1] & 0x20) >> 5); // B
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[0] & 0x01) << 2) | ((s[0] & 0x04) >> 1) | ((s[0] & 0x10) >> 4); // C
|
||||
sqwk *= 10;
|
||||
sqwk += ((s[1] & 0x01) << 2) | ((s[1] & 0x04) >> 1) | ((s[1] & 0x10) >> 4); // D
|
||||
|
||||
log_entry.sqwk = entry.sqwk = sqwk;
|
||||
}
|
||||
|
||||
if (df == 20 || df == 21) {
|
||||
if (raw_data[4] == 0x20) { // try decode as BDS20
|
||||
std::string callsign = decode_frame_id(frame);
|
||||
if (callsign.find('#') == std::string::npos) { // all chars OK
|
||||
entry.set_callsign(callsign);
|
||||
log_entry.callsign = callsign;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger->log(log_entry);
|
||||
}
|
||||
|
||||
void ADSBRxView::on_tick_second() {
|
||||
@@ -544,7 +654,7 @@ void ADSBRxView::refresh_ui() {
|
||||
}
|
||||
|
||||
// NB: current entry also gets a marker so it shows up if map is panned.
|
||||
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
|
||||
if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
|
||||
map_needs_update = details_view->add_map_marker(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,8 +95,8 @@ struct AircraftRecentEntry {
|
||||
ADSBAgeState state{ADSBAgeState::Invalid};
|
||||
uint32_t age{0}; // In seconds
|
||||
uint32_t amp{0};
|
||||
adsb_pos pos{false, 0, 0, 0};
|
||||
adsb_vel velo{false, 0, 999, 0};
|
||||
adsb_pos pos{false, false, 0, 0, 0};
|
||||
adsb_vel velo{false, SPD_GND, 0, 999, 0};
|
||||
ADSBFrame frame_pos_even{};
|
||||
ADSBFrame frame_pos_odd{};
|
||||
|
||||
@@ -105,6 +105,7 @@ struct AircraftRecentEntry {
|
||||
std::string info_string{};
|
||||
|
||||
uint8_t sil{0}; // Surveillance integrity level
|
||||
uint16_t sqwk{0};
|
||||
|
||||
AircraftRecentEntry(const uint32_t ICAO_address)
|
||||
: ICAO_address{ICAO_address} {
|
||||
@@ -152,8 +153,8 @@ struct AircraftRecentEntry {
|
||||
age += delta;
|
||||
|
||||
if (age < ADSBAgeLimit::Current)
|
||||
state = pos.valid ? ADSBAgeState::Invalid
|
||||
: ADSBAgeState::Current;
|
||||
state = pos.pos_valid ? ADSBAgeState::Current
|
||||
: ADSBAgeState::Invalid;
|
||||
|
||||
else if (age < ADSBAgeLimit::Recent)
|
||||
state = ADSBAgeState::Recent;
|
||||
@@ -178,6 +179,7 @@ struct ADSBLogEntry {
|
||||
adsb_vel vel{};
|
||||
uint8_t vel_type{};
|
||||
uint8_t sil{};
|
||||
uint16_t sqwk{};
|
||||
};
|
||||
|
||||
// TODO: Make logging optional.
|
||||
|
||||
@@ -92,6 +92,9 @@ void ExternalModuleView::on_tick_second() {
|
||||
case app_location_t::TX:
|
||||
btnText += " (TX)";
|
||||
break;
|
||||
case app_location_t::TRX:
|
||||
btnText += " (TRX)";
|
||||
break;
|
||||
case app_location_t::SETTINGS:
|
||||
btnText += " (Settings)";
|
||||
break;
|
||||
|
||||
@@ -419,6 +419,17 @@ void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void FileManBaseView::copy_waterfall(std::filesystem::path path) {
|
||||
nav_.push<ModalMessageView>(
|
||||
"Install", " Use this gradient file\n for all waterfalls?", YESNO,
|
||||
[this, path](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(default_gradient_file);
|
||||
copy_file(path, default_gradient_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const {
|
||||
size_t index = 0;
|
||||
@@ -685,7 +696,11 @@ bool FileManagerView::handle_file_open() {
|
||||
auto ext = path.extension();
|
||||
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
if (path_iequal(current_path, u"/" + waterfalls_dir)) {
|
||||
copy_waterfall(path);
|
||||
} else {
|
||||
nav_.push<TextEditorView>(path);
|
||||
}
|
||||
return true;
|
||||
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
|
||||
@@ -96,6 +96,7 @@ class FileManBaseView : public View {
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
void copy_waterfall(std::filesystem::path path);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_modulations;
|
||||
extern options_db_t freqman_bandwidths[5];
|
||||
extern options_db_t freqman_steps;
|
||||
extern options_db_t freqman_bandwidths[6];
|
||||
// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
|
||||
extern options_db_t freqman_steps_short;
|
||||
|
||||
options_t dboptions_to_options(const options_db_t& dboptions) {
|
||||
|
||||
@@ -126,7 +126,7 @@ void GlassView::reset_live_view() {
|
||||
}
|
||||
|
||||
void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
spectrum_row[pixel_index] = spectrum_rgb3_lut[power]; // row of colors
|
||||
spectrum_row[pixel_index] = gradient.lut[power]; // row of colors
|
||||
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
|
||||
pixel_index++;
|
||||
|
||||
@@ -359,6 +359,10 @@ GlassView::GlassView(
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
&field_frequency_max,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -74,6 +74,7 @@ class GlassView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||
// Settings
|
||||
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
|
||||
|
||||
@@ -56,6 +56,10 @@ SearchView::SearchView(
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
&field_frequency_max,
|
||||
@@ -290,7 +294,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
power = spectrum.db[bin - 128];
|
||||
}
|
||||
|
||||
add_spectrum_pixel(spectrum_rgb3_lut[power]);
|
||||
add_spectrum_pixel(gradient.lut[power]);
|
||||
|
||||
mean_acc += power;
|
||||
if (power > max_power) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "receiver_model.hpp"
|
||||
#include "recent_entries.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -88,6 +88,7 @@ class SearchView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{
|
||||
100'000'000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
|
||||
@@ -810,30 +810,29 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_invert_switch,
|
||||
&checkbox_ips_screen_switch,
|
||||
&checkbox_brightness_switch});
|
||||
|
||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
||||
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
|
||||
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
|
||||
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
|
||||
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
|
||||
display.set_inverted(checkbox_invert_switch.value());
|
||||
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
|
||||
if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
|
||||
pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
|
||||
}
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
// only enable invert OR fake brightness
|
||||
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
|
||||
checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_brightness_switch.set_value(false);
|
||||
};
|
||||
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_invert_switch.set_value(false);
|
||||
if (v) checkbox_ips_screen_switch.set_value(false);
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
|
||||
@@ -762,6 +762,8 @@ class SetDisplayView : public View {
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_fake_brightness{
|
||||
@@ -776,10 +778,10 @@ class SetDisplayView : public View {
|
||||
16,
|
||||
"Enable brightness adjust"};
|
||||
|
||||
Checkbox checkbox_invert_switch{
|
||||
{1 * 8, 10 * 16},
|
||||
Checkbox checkbox_ips_screen_switch{
|
||||
{1 * 8, 12 * 16},
|
||||
23,
|
||||
"Invert colors (For IPS)"};
|
||||
"IPS Screen"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
|
||||
@@ -154,6 +154,13 @@ SigGenView::SigGenView(
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_bandwidth_changed = [this]() {
|
||||
// we don't protect here with auto_update because other field of tx_view obj isn't protected too
|
||||
// to remains the design logic same
|
||||
|
||||
update_config();
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
start_tx();
|
||||
tx_view.set_transmitting(true);
|
||||
@@ -165,4 +172,4 @@ SigGenView::SigGenView(
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
@@ -93,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
|
||||
|
||||
void WFMConfig::apply() const {
|
||||
const WFMConfigureMessage message{
|
||||
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
|
||||
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
|
||||
taps_64_lp_156_198,
|
||||
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
|
||||
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
|
||||
taps_64_lp_156_198, // Fixed channel audio filter 15khz
|
||||
75000,
|
||||
audio_48k_hpf_30hz_config,
|
||||
audio_48k_deemph_2122_6_config};
|
||||
@@ -103,6 +103,18 @@ void WFMConfig::apply() const {
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
}
|
||||
|
||||
void WFMAMConfig::apply() const {
|
||||
const WFMAMConfigureMessage message{
|
||||
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
|
||||
decim_1, // Fixed 32 taps array : taps_84k_wfm_decim_1
|
||||
taps_64_lp_1875_2166, // Fixed channel audio filter , 64 taps array , to filter DSB AM 2k4 carrier before demod. AM .
|
||||
17000, // NOAA satellite tx , FM deviation = +-17Khz.
|
||||
apt_audio_12k_notch_2k4_config,
|
||||
apt_audio_12k_lpf_2000hz_config};
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
|
||||
@@ -310,6 +322,11 @@ void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_noaaapt_config() {
|
||||
const NoaaAptRxConfigureMessage message{};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_siggen_tone(const uint32_t tone) {
|
||||
const SigGenToneMessage message{
|
||||
TONES_F2D(tone, TONES_SAMPLERATE)};
|
||||
|
||||
@@ -56,12 +56,19 @@ struct NBFMConfig {
|
||||
};
|
||||
|
||||
struct WFMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
|
||||
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 40K-
|
||||
const fir_taps_real<16> decim_1;
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
struct WFMAMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
|
||||
const fir_taps_real<32> decim_1;
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
@@ -93,6 +100,7 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
|
||||
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
||||
void set_noaaapt_config();
|
||||
|
||||
void request_roger_beep();
|
||||
void request_rssi_beep();
|
||||
|
||||
+5309
-5233
File diff suppressed because it is too large
Load Diff
@@ -80,7 +80,7 @@ bool DebugDumpView::debug_dump_func() {
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(config_updown_converter()));
|
||||
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(config_freq_rx_correction_updown()));
|
||||
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(config_freq_tx_correction_updown()));
|
||||
pmem_dump_file.write_line("lcd_inverted_mode: " + to_string_dec_uint(config_lcd_inverted_mode()));
|
||||
pmem_dump_file.write_line("lcd_normally_black: " + to_string_dec_uint(config_lcd_normally_black()));
|
||||
pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(config_converter_freq()));
|
||||
pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(config_freq_rx_correction()));
|
||||
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(config_freq_tx_correction()));
|
||||
@@ -157,6 +157,9 @@ bool DebugDumpView::debug_dump_func() {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
pmem_dump_file.write_line("modulation: Mode::WidebandFMAudio");
|
||||
break;
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
pmem_dump_file.write_line("modulation: Mode::WFMAudioAMApt");
|
||||
break;
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
pmem_dump_file.write_line("modulation: Mode::SpectrumAnalysis");
|
||||
break;
|
||||
|
||||
+11
@@ -111,6 +111,11 @@ set(EXTCPPSRC
|
||||
external/wefax_rx/main.cpp
|
||||
external/wefax_rx/ui_wefax_rx.cpp
|
||||
|
||||
#noaaapt_rx
|
||||
external/noaaapt_rx/main.cpp
|
||||
external/noaaapt_rx/ui_noaaapt_rx.cpp
|
||||
|
||||
|
||||
|
||||
#shoppingcart_lock
|
||||
external/shoppingcart_lock/main.cpp
|
||||
@@ -203,6 +208,10 @@ set(EXTCPPSRC
|
||||
#level
|
||||
external/level/main.cpp
|
||||
external/level/ui_level.cpp
|
||||
|
||||
#gfxEQ
|
||||
external/gfxeq/main.cpp
|
||||
external/gfxeq/ui_gfxeq.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -235,6 +244,7 @@ set(EXTAPPLIST
|
||||
ookbrute
|
||||
ook_editor
|
||||
wefax_rx
|
||||
noaaapt_rx
|
||||
shoppingcart_lock
|
||||
flippertx
|
||||
remote
|
||||
@@ -255,4 +265,5 @@ set(EXTAPPLIST
|
||||
debug_pmem
|
||||
scanner
|
||||
level
|
||||
gfxeq
|
||||
)
|
||||
|
||||
+15
@@ -72,6 +72,8 @@ MEMORY
|
||||
ram_external_app_debug_pmem (rwx) : org = 0xADDF0000, len = 32k
|
||||
ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k
|
||||
ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
|
||||
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
|
||||
ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -333,12 +335,19 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_stopwatch.application_information));
|
||||
*(*ui*external_app*stopwatch*);
|
||||
} > ram_external_app_stopwatch
|
||||
|
||||
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_wefax_rx.application_information));
|
||||
*(*ui*external_app*wefax_rx*);
|
||||
} > ram_external_app_wefax_rx
|
||||
|
||||
.external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_noaaapt_rx.application_information));
|
||||
*(*ui*external_app*noaaapt_rx*);
|
||||
} > ram_external_app_noaaapt_rx
|
||||
|
||||
.external_app_breakout : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_breakout.application_information));
|
||||
@@ -368,4 +377,10 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_level.application_information));
|
||||
*(*ui*external_app*level*);
|
||||
} > ram_external_app_level
|
||||
|
||||
.external_app_gfxeq : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_gfxeq.application_information));
|
||||
*(*ui*external_app*gfxeq*);
|
||||
} > ram_external_app_gfxeq
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_gfxeq.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<gfxEQView>();
|
||||
}
|
||||
} // namespace ui::external_app::gfxeq
|
||||
|
||||
extern "C" {
|
||||
__attribute__((section(".external_app.app_gfxeq.application_information"), used)) application_information_t _application_information_gfxeq = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::gfxeq::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"gfxEQ",
|
||||
{0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||
ui::Color::green().v,
|
||||
app_location_t::RX,
|
||||
-1,
|
||||
{'P', 'W', 'F', 'M'},
|
||||
0x00000000,
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::gfxeq
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_gfxeq.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
|
||||
gfxEQView::gfxEQView(NavigationView& nav)
|
||||
: nav_{nav}, bar_heights(NUM_BARS, 0), prev_bar_heights(NUM_BARS, 0) {
|
||||
add_children({&button_frequency, &field_rf_amp, &field_lna, &field_vga,
|
||||
&button_mood, &field_volume});
|
||||
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.set_wfm_configuration(1); // 200k => 0 , 180k => 1 , 40k => 2. Set to 1 or 2 for better reception
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
|
||||
//
|
||||
receiver_model.enable();
|
||||
|
||||
receiver_model.set_target_frequency(frequency_value); // Retune to actual freq
|
||||
button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
|
||||
|
||||
button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(frequency_value);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
frequency_value = f;
|
||||
receiver_model.set_target_frequency(f); // Retune to actual freq
|
||||
button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
|
||||
};
|
||||
};
|
||||
|
||||
button_frequency.on_change = [this]() {
|
||||
int64_t def_step = 25000;
|
||||
frequency_value = frequency_value + (button_frequency.get_encoder_delta() * def_step);
|
||||
if (frequency_value < 1) {
|
||||
frequency_value = 1;
|
||||
}
|
||||
if (frequency_value > (MAX_UFREQ - def_step)) {
|
||||
frequency_value = MAX_UFREQ;
|
||||
}
|
||||
button_frequency.set_encoder_delta(0);
|
||||
receiver_model.set_target_frequency(frequency_value); // Retune to actual freq
|
||||
button_frequency.set_text("<" + to_string_short_freq(frequency_value) + ">");
|
||||
};
|
||||
|
||||
button_mood.on_select = [this](Button&) { this->cycle_theme(); };
|
||||
}
|
||||
|
||||
// needed to answer usb serial frequency set
|
||||
void gfxEQView::on_freqchg(int64_t freq) {
|
||||
receiver_model.set_target_frequency(freq); // Retune to actual freq
|
||||
button_frequency.set_text("<" + to_string_short_freq(freq) + ">");
|
||||
}
|
||||
|
||||
gfxEQView::~gfxEQView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void gfxEQView::focus() {
|
||||
button_frequency.focus();
|
||||
}
|
||||
|
||||
void gfxEQView::on_show() {
|
||||
needs_background_redraw = true;
|
||||
}
|
||||
|
||||
void gfxEQView::on_hide() {
|
||||
needs_background_redraw = true;
|
||||
}
|
||||
|
||||
void gfxEQView::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
const float bin_frequency_size = 48000.0f / 128;
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
float start_freq = FREQUENCY_BANDS[bar];
|
||||
float end_freq = FREQUENCY_BANDS[bar + 1];
|
||||
|
||||
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
|
||||
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
|
||||
|
||||
if (start_bin >= end_bin) {
|
||||
end_bin = start_bin + 1;
|
||||
}
|
||||
|
||||
float total_energy = 0;
|
||||
int bin_count = 0;
|
||||
|
||||
for (int bin = start_bin; bin <= end_bin; bin++) {
|
||||
total_energy += spectrum.db[bin];
|
||||
bin_count++;
|
||||
}
|
||||
|
||||
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
|
||||
|
||||
// Manually boost highs for better visual balance
|
||||
float treble_boost = 1.0f;
|
||||
if (bar == 10)
|
||||
treble_boost = 1.7f;
|
||||
else if (bar >= 9)
|
||||
treble_boost = 1.3f;
|
||||
else if (bar >= 7)
|
||||
treble_boost = 1.3f;
|
||||
|
||||
// Mid emphasis for a V-shape effect
|
||||
float mid_boost = 1.0f;
|
||||
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
|
||||
|
||||
float amplified_db = avg_db * treble_boost * mid_boost;
|
||||
|
||||
if (amplified_db > 255) amplified_db = 255;
|
||||
|
||||
float band_scale = 1.0f;
|
||||
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
|
||||
|
||||
if (target_height > RENDER_HEIGHT) {
|
||||
target_height = RENDER_HEIGHT;
|
||||
}
|
||||
|
||||
// Adjusted to look nice to my eyes
|
||||
float rise_speed = 0.8f;
|
||||
float fall_speed = 1.0f;
|
||||
|
||||
if (target_height > bar_heights[bar]) {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
|
||||
} else {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gfxEQView::render_equalizer(Painter& painter) {
|
||||
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
|
||||
const ColorTheme& theme = themes[current_theme];
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
|
||||
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
|
||||
|
||||
if (prev_bar_heights[bar] > active_segments) {
|
||||
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
|
||||
int clear_y = SCREEN_HEIGHT - prev_bar_heights[bar] * SEGMENT_HEIGHT;
|
||||
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Color(0, 0, 0));
|
||||
}
|
||||
|
||||
for (int seg = 0; seg < active_segments; seg++) {
|
||||
int y = SCREEN_HEIGHT - (seg + 1) * SEGMENT_HEIGHT;
|
||||
if (y < header_height) break;
|
||||
|
||||
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? theme.peak_color : theme.base_color;
|
||||
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
|
||||
}
|
||||
|
||||
prev_bar_heights[bar] = active_segments;
|
||||
}
|
||||
}
|
||||
|
||||
void gfxEQView::paint(Painter& painter) {
|
||||
if (needs_background_redraw) {
|
||||
painter.fill_rectangle({0, header_height, SCREEN_WIDTH, RENDER_HEIGHT}, Color(0, 0, 0));
|
||||
needs_background_redraw = false;
|
||||
}
|
||||
render_equalizer(painter);
|
||||
}
|
||||
|
||||
void gfxEQView::cycle_theme() {
|
||||
current_theme = (current_theme + 1) % themes.size();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::gfxeq
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_GFXEQ_HPP__
|
||||
#define __UI_GFXEQ_HPP__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "message.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
|
||||
class gfxEQView : public View {
|
||||
public:
|
||||
gfxEQView(NavigationView& nav);
|
||||
~gfxEQView();
|
||||
|
||||
gfxEQView(const gfxEQView&) = delete;
|
||||
gfxEQView& operator=(const gfxEQView&) = delete;
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "gfxEQ"; }
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_freqchg(int64_t freq);
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 2 * 16;
|
||||
static constexpr int SCREEN_WIDTH = 240;
|
||||
static constexpr int SCREEN_HEIGHT = 320;
|
||||
static constexpr int RENDER_HEIGHT = 288;
|
||||
static constexpr int NUM_BARS = 11;
|
||||
static constexpr int BAR_SPACING = 2;
|
||||
static constexpr int BAR_WIDTH = (SCREEN_WIDTH - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
|
||||
static constexpr int HORIZONTAL_OFFSET = 2;
|
||||
static constexpr int SEGMENT_HEIGHT = 10;
|
||||
|
||||
static constexpr std::array<int, NUM_BARS + 1> FREQUENCY_BANDS = {
|
||||
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
|
||||
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
|
||||
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
|
||||
2250, // Midrange clarity (e.g., vocal presence, snare crack)
|
||||
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
|
||||
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
|
||||
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
|
||||
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
|
||||
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
|
||||
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
|
||||
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
|
||||
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
|
||||
};
|
||||
|
||||
struct ColorTheme {
|
||||
Color base_color;
|
||||
Color peak_color;
|
||||
};
|
||||
|
||||
NavigationView& nav_;
|
||||
bool needs_background_redraw{false};
|
||||
std::vector<int> bar_heights;
|
||||
std::vector<int> prev_bar_heights;
|
||||
uint32_t current_theme{0};
|
||||
const std::array<ColorTheme, 20> themes{
|
||||
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
|
||||
ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
|
||||
ColorTheme{Color(0, 0, 255), Color(255, 255, 0)},
|
||||
ColorTheme{Color(255, 128, 0), Color(255, 0, 128)},
|
||||
ColorTheme{Color(128, 0, 255), Color(0, 255, 255)},
|
||||
ColorTheme{Color(255, 255, 0), Color(0, 255, 128)},
|
||||
ColorTheme{Color(255, 0, 0), Color(0, 128, 255)},
|
||||
ColorTheme{Color(0, 255, 128), Color(255, 128, 255)},
|
||||
ColorTheme{Color(128, 128, 128), Color(255, 255, 255)},
|
||||
ColorTheme{Color(255, 64, 0), Color(0, 255, 64)},
|
||||
ColorTheme{Color(0, 128, 128), Color(255, 192, 0)},
|
||||
ColorTheme{Color(0, 255, 0), Color(0, 128, 0)},
|
||||
ColorTheme{Color(32, 64, 32), Color(0, 255, 0)},
|
||||
ColorTheme{Color(64, 0, 128), Color(255, 0, 255)},
|
||||
ColorTheme{Color(0, 64, 0), Color(0, 255, 128)},
|
||||
ColorTheme{Color(255, 255, 255), Color(0, 0, 255)},
|
||||
ColorTheme{Color(128, 0, 0), Color(255, 128, 0)},
|
||||
ColorTheme{Color(0, 128, 255), Color(255, 255, 128)},
|
||||
ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
|
||||
ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
|
||||
|
||||
ButtonWithEncoder button_frequency{{0 * 8, 0 * 16 + 4, 11 * 8, 1 * 8}, ""};
|
||||
RFAmpField field_rf_amp{{13 * 8, 0 * 16}};
|
||||
LNAGainField field_lna{{15 * 8, 0 * 16}};
|
||||
VGAGainField field_vga{{18 * 8, 0 * 16}};
|
||||
Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
|
||||
AudioVolumeField field_volume{{28 * 8, 0 * 16}};
|
||||
|
||||
rf::Frequency frequency_value{93100000};
|
||||
|
||||
RxRadioState rx_radio_state_{};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_gfx_eq",
|
||||
app_settings::Mode::RX,
|
||||
{{"theme", ¤t_theme},
|
||||
{"frequency", &frequency_value}}};
|
||||
|
||||
void update_audio_spectrum(const AudioSpectrum& spectrum);
|
||||
void render_equalizer(Painter& painter);
|
||||
void cycle_theme();
|
||||
|
||||
MessageHandlerRegistration message_handler_audio_spectrum{
|
||||
Message::ID::AudioSpectrum,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
|
||||
this->update_audio_spectrum(*message.data);
|
||||
this->set_dirty();
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_freqchg{
|
||||
Message::ID::FreqChangeCommand,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||
this->on_freqchg(message->freq);
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::gfxeq
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -77,8 +77,8 @@ __attribute__((section(".external_app.app_level.application_information"), used)
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
// this has to be the biggest baseband used by the app. Level is using AM,WFM,NFM,AMFM,SPEC and NFM is the biggest
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'N', 'F', 'M'},
|
||||
// this has to be the biggest baseband used by the app. Level is using AM,WFM,NFM,AMFM,SPEC and WFM is the biggest
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* 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.hpp"
|
||||
#include "ui_noaaapt_rx.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::noaaapt_rx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<NoaaAptRxView>();
|
||||
}
|
||||
} // namespace ui::external_app::noaaapt_rx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_noaaapt_rx.application_information"), used)) application_information_t _application_information_noaaapt_rx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::noaaapt_rx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "NOAA APT",
|
||||
/*.bitmap_data = */ {
|
||||
0x1C,
|
||||
0x80,
|
||||
0x3C,
|
||||
0x40,
|
||||
0x78,
|
||||
0x18,
|
||||
0xF0,
|
||||
0x20,
|
||||
0xE0,
|
||||
0x26,
|
||||
0x00,
|
||||
0x0F,
|
||||
0x80,
|
||||
0x0F,
|
||||
0xC0,
|
||||
0x07,
|
||||
0xE1,
|
||||
0x1B,
|
||||
0xC5,
|
||||
0x39,
|
||||
0x95,
|
||||
0x78,
|
||||
0x35,
|
||||
0xF0,
|
||||
0x09,
|
||||
0xE0,
|
||||
0x72,
|
||||
0xC0,
|
||||
0x04,
|
||||
0x00,
|
||||
0x78,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_noaaapt_rx */ {'P', 'N', 'O', 'A'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
TODOS LATER:
|
||||
- add load data from wav file (maybe to a separate app, not this)
|
||||
- AGC?!?
|
||||
- Auto doppler correction
|
||||
- fix and enable sync detection
|
||||
- auto start / stop bmp save on each image
|
||||
*/
|
||||
|
||||
#include "ui_noaaapt_rx.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lcd_ili9341.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::noaaapt_rx {
|
||||
|
||||
void NoaaAptRxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
NoaaAptRxView::NoaaAptRxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
add_children({&rssi,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_volume,
|
||||
&field_frequency,
|
||||
&txt_status,
|
||||
//&check_wav, // enable this or the record view, but not both. yet it has some error, says "Invalid object" a lot. so disabled it
|
||||
//&record_view, //
|
||||
&button_ss});
|
||||
|
||||
record_view.set_filename_date_frequency(true);
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
record_view.set_sampling_rate(12000);
|
||||
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_edit_shown = [this]() {
|
||||
paused = true;
|
||||
};
|
||||
field_frequency.on_edit_hidden = [this]() {
|
||||
paused = false;
|
||||
};
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
audio::output::start();
|
||||
receiver_model.set_hidden_offset(NOAAAPT_FREQ_OFFSET);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.enable();
|
||||
txt_status.set("Waiting for signal.");
|
||||
|
||||
button_ss.on_select = [this](Button&) {
|
||||
if (bmp.is_loaded()) {
|
||||
bmp.close();
|
||||
button_ss.set_text(LanguageHelper::currentMessages[LANG_START]);
|
||||
if (check_wav.value()) {
|
||||
record_view.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (check_wav.value()) {
|
||||
record_view.start();
|
||||
}
|
||||
ensure_directory("/BMP");
|
||||
bmp.create("/BMP/noaa_" + to_string_timestamp(rtc_time::now()) + ".bmp", NOAAAPT_PX_SIZE, 1);
|
||||
|
||||
button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]);
|
||||
};
|
||||
on_settings_changed();
|
||||
}
|
||||
|
||||
NoaaAptRxView::~NoaaAptRxView() {
|
||||
stopping = true;
|
||||
receiver_model.set_hidden_offset(0);
|
||||
bmp.close();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
audio::output::stop();
|
||||
record_view.stop();
|
||||
}
|
||||
|
||||
void NoaaAptRxView::on_settings_changed() {
|
||||
baseband::set_noaaapt_config();
|
||||
}
|
||||
|
||||
void NoaaAptRxView::on_status(NoaaAptRxStatusDataMessage msg) {
|
||||
(void)msg;
|
||||
std::string tmp = "";
|
||||
if (msg.state == 0) {
|
||||
tmp = "Waiting for signal.";
|
||||
} else if (msg.state == 1) {
|
||||
tmp = "Synced.";
|
||||
} else if (msg.state == 2) {
|
||||
tmp = "Image arriving.";
|
||||
}
|
||||
txt_status.set(tmp);
|
||||
}
|
||||
|
||||
// this stores and displays the image. keep it as simple as you can. a bit more complexity will kill the sync
|
||||
void NoaaAptRxView::on_image(NoaaAptRxImageDataMessage msg) {
|
||||
if ((line_num) >= 320 - NOAA_IMG_START_ROW * 16) line_num = 0; // for draw reset
|
||||
|
||||
for (uint16_t i = 0; i < msg.cnt; i += 1) {
|
||||
Color pxl = {msg.image[i], msg.image[i], msg.image[i]};
|
||||
bmp.write_next_px(pxl);
|
||||
line_in_part++;
|
||||
if (line_in_part == NOAAAPT_PX_SIZE) {
|
||||
line_in_part = 0;
|
||||
line_num++;
|
||||
bmp.expand_y_delta(1);
|
||||
}
|
||||
|
||||
uint16_t xpos = line_in_part / (NOAAAPT_PX_SIZE / 240);
|
||||
if (xpos >= 240) xpos = 239;
|
||||
line_buffer[xpos] = pxl;
|
||||
if ((line_in_part == 0)) {
|
||||
portapack::display.render_line({0, line_num + NOAA_IMG_START_ROW * 16}, 240, line_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::noaaapt_rx
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* 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_noaaapt_RX_H__
|
||||
#define __UI_noaaapt_RX_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "bmpfile.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::noaaapt_rx {
|
||||
|
||||
#define NOAAAPT_PX_SIZE 2080
|
||||
|
||||
#define NOAAAPT_FREQ_OFFSET 0
|
||||
|
||||
#define NOAA_IMG_START_ROW 4
|
||||
|
||||
class NoaaAptRxView : public View {
|
||||
public:
|
||||
NoaaAptRxView(NavigationView& nav);
|
||||
~NoaaAptRxView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "NOAA APT"; };
|
||||
|
||||
private:
|
||||
void on_settings_changed();
|
||||
void on_status(NoaaAptRxStatusDataMessage msg);
|
||||
void on_image(NoaaAptRxImageDataMessage msg);
|
||||
|
||||
bool stopping = false;
|
||||
|
||||
uint16_t line_num = 0; // nth line
|
||||
uint16_t line_in_part = 0; // got multiple parts of a line, so keep track of it
|
||||
uint8_t delayer = 0;
|
||||
ui::Color line_buffer[240];
|
||||
std::filesystem::path filetohandle = "";
|
||||
|
||||
bool paused = false; // when freq field is shown for example, we need to pause
|
||||
|
||||
BMPFile bmp{};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_noaaapt",
|
||||
app_settings::Mode::RX,
|
||||
{}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
u"AUD",
|
||||
u"AUDIO",
|
||||
RecordView::FileType::WAV,
|
||||
4096,
|
||||
4};
|
||||
|
||||
Checkbox check_wav{
|
||||
{0 * 8, 2 * 16},
|
||||
12,
|
||||
"Save WAV too",
|
||||
true};
|
||||
|
||||
/*Labels labels{
|
||||
{{1 * 8, 1 * 16}, "LPM:", Theme::getInstance()->fg_light->foreground},
|
||||
{{13 * 8, 1 * 16}, "IOC:", Theme::getInstance()->fg_light->foreground},
|
||||
};*/
|
||||
|
||||
Text txt_status{
|
||||
{0 * 8, 1 * 16, 20 * 8, 16},
|
||||
};
|
||||
|
||||
Button button_ss{
|
||||
{190, 1 * 16, 5 * 8, 16},
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats{
|
||||
Message::ID::NoaaAptRxStatusData,
|
||||
[this](const Message* const p) {
|
||||
if (stopping || paused) return;
|
||||
const auto message = *reinterpret_cast<const NoaaAptRxStatusDataMessage*>(p);
|
||||
on_status(message);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_image{
|
||||
Message::ID::NoaaAptRxImageData,
|
||||
[this](const Message* const p) {
|
||||
if (stopping || paused) return;
|
||||
const auto message = *reinterpret_cast<const NoaaAptRxImageDataMessage*>(p);
|
||||
on_image(message);
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::noaaapt_rx
|
||||
|
||||
#endif /*__UI_noaaapt_RX_H__*/
|
||||
+1
-1
@@ -75,7 +75,7 @@ __attribute__((section(".external_app.app_remote.application_information"), used
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::HOME,
|
||||
/*.desired_menu_position = */ 4,
|
||||
/*.desired_menu_position = */ 6,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_replay */ {'P', 'R', 'E', 'P'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ __attribute__((section(".external_app.app_scanner.application_information"), use
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
// this has to be the biggest baseband used by the app. Scanner is using AM,WFM,NFM and NFM is the biggest
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_scanner */ {'P', 'N', 'F', 'M'},
|
||||
// this has to be the biggest baseband used by the app. Scanner is using AM,WFM,NFM and WFM is the biggest
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_scanner */ {'P', 'W', 'F', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
|
||||
+13
-4
@@ -259,8 +259,12 @@ void Tetromino(unsigned char c) {
|
||||
|
||||
void Initialize(unsigned char c) {
|
||||
colorIndex = c;
|
||||
boardX = 0;
|
||||
boardY = 4;
|
||||
if (c == 1) { // I-tetromino
|
||||
boardX = -1; // Spawn higher
|
||||
} else {
|
||||
boardX = 0; // Other tetrominos spawn at top
|
||||
}
|
||||
copyCoordinates(X, Y, c - 1);
|
||||
rotation_state = 0;
|
||||
}
|
||||
@@ -269,7 +273,7 @@ void Rotate() {
|
||||
short newX[4], newY[4];
|
||||
int next_state = (rotation_state + 1) % 4;
|
||||
|
||||
if (colorIndex == 2) {
|
||||
if (colorIndex == 2) { // O-tetromino doesn't rotate
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,6 +295,7 @@ void Rotate() {
|
||||
short kickX = kick_tests[rotation_state][test][0];
|
||||
short kickY = kick_tests[rotation_state][test][1];
|
||||
|
||||
// Calculate new positions
|
||||
for (int i = 0; i < 4; i++) {
|
||||
short tmpX = X[i] - X[1];
|
||||
short tmpY = Y[i] - Y[1];
|
||||
@@ -298,11 +303,13 @@ void Rotate() {
|
||||
newY[i] = Y[1] + tmpX;
|
||||
int testX = boardX + newX[i] + kickX;
|
||||
int testY = boardY + newY[i] + kickY;
|
||||
if (OutOfBounds(testY, testX) || (testX >= 0 && board[testX][testY] != 0)) {
|
||||
// Explicitly block rotations that place blocks above the board
|
||||
if (testX < 0 || OutOfBounds(testY, testX) || (testX >= 0 && board[testX][testY] != 0)) {
|
||||
goto next_test;
|
||||
}
|
||||
}
|
||||
|
||||
// Valid rotation found, apply it
|
||||
DeleteFigure();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
X[i] = newX[i];
|
||||
@@ -492,7 +499,9 @@ void UpdateBoard() {
|
||||
|
||||
bool IsOver() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (board[0][i] != 0) return true;
|
||||
if (board[0][i] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ WeFaxRxView::WeFaxRxView(NavigationView& nav)
|
||||
};
|
||||
audio::output::start();
|
||||
receiver_model.set_hidden_offset(WEFAX_FREQ_OFFSET);
|
||||
receiver_model.set_sampling_rate(3072000); // set the needed baseband SR.
|
||||
receiver_model.set_baseband_bandwidth(1750000); // set the front-end RF BW filter.
|
||||
receiver_model.enable();
|
||||
|
||||
txt_status.set("Waiting for signal.");
|
||||
|
||||
@@ -51,3 +51,4 @@ const std::filesystem::path whipcalc_dir = u"WHIPCALC";
|
||||
const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
|
||||
|
||||
@@ -53,5 +53,6 @@ extern const std::filesystem::path whipcalc_dir;
|
||||
extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
extern const std::filesystem::path waterfalls_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -42,7 +42,8 @@ enum freqman_entry_modulation : uint8_t {
|
||||
NFM_MODULATION,
|
||||
WFM_MODULATION,
|
||||
SPEC_MODULATION,
|
||||
AMFM_MODULATION // Added for Wefax.
|
||||
AMFM_MODULATION, // Added for HF Wefax.demod APT signal
|
||||
WFMAM_MODULATION // Added for NOAA 137 Mhz satellite band, demod APT signal.
|
||||
};
|
||||
|
||||
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
|
||||
|
||||
@@ -49,10 +49,11 @@ options_t freqman_modulations = {
|
||||
{"NFM", 1},
|
||||
{"WFM", 2},
|
||||
{"SPEC", 3},
|
||||
{"AMFM", 4},
|
||||
{"AMFM", 4}, // To handle HF Wefax AM and FM demod. inside Audio App.
|
||||
{"FMAM", 5}, // To handle NOAA 137 Mhz Sat FM and AM demod inside Audio App.
|
||||
};
|
||||
|
||||
options_t freqman_bandwidths[5] = {
|
||||
options_t freqman_bandwidths[6] = {
|
||||
{
|
||||
// AM
|
||||
{"DSB 9k", 0},
|
||||
@@ -103,12 +104,19 @@ options_t freqman_bandwidths[5] = {
|
||||
},
|
||||
{
|
||||
// AMFM for Wefax-
|
||||
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones.
|
||||
{"USB+FM(Wefax Apt)", 5}, // Fixed RX demod. AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones.
|
||||
},
|
||||
{
|
||||
// WFMAM for NOAA satellites, 137 Mhz band
|
||||
{"80kHz (NOAA Apt)", 0}, // Fixed RX demod- WFM config Index 1 : FM+AM for Audio NOAA APT ones.
|
||||
{"38kHz (NOAA Apt)", 1},
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps = {
|
||||
{"10Hz ", 10},
|
||||
{"50Hz ", 50},
|
||||
{"0.1kHz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
{"5kHz (SA AM)", 5000},
|
||||
@@ -129,6 +137,8 @@ options_t freqman_steps = {
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps_short = {
|
||||
{"10Hz", 10},
|
||||
{"50Hz", 50},
|
||||
{"0.1kHz", 100},
|
||||
{"1kHz", 1000},
|
||||
{"5kHz", 5000},
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Belousov Oleg
|
||||
*
|
||||
* 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 "gradient.hpp"
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const std::filesystem::path default_gradient_file = u"waterfall.txt";
|
||||
|
||||
Gradient::Gradient() {
|
||||
prev_index = 0;
|
||||
prev_r = 0;
|
||||
prev_g = 0;
|
||||
prev_b = 0;
|
||||
}
|
||||
|
||||
void Gradient::set_default() {
|
||||
step(86, 0, 0, 255);
|
||||
step(171, 0, 255, 0);
|
||||
step(255, 255, 0, 0);
|
||||
}
|
||||
|
||||
bool Gradient::load_file(const std::filesystem::path& file_path) {
|
||||
File gradient_file;
|
||||
auto error = gradient_file.open(file_path.string());
|
||||
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
auto reader = FileLineReader(gradient_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue; // Empty or comment line.
|
||||
|
||||
auto cols = split_string(line, ',');
|
||||
|
||||
if (cols.size() == 4) {
|
||||
int16_t index, r, g, b;
|
||||
|
||||
if (!parse_int(cols[0], index) || index < 0 || index > 255)
|
||||
continue;
|
||||
|
||||
if (!parse_int(cols[1], r) || r < 0 || r > 255)
|
||||
continue;
|
||||
|
||||
if (!parse_int(cols[2], g) || g < 0 || g > 255)
|
||||
continue;
|
||||
|
||||
if (!parse_int(cols[3], b) || b < 0 || b > 255)
|
||||
continue;
|
||||
|
||||
step(index, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gradient::step(int16_t index, int16_t r, int16_t g, int16_t b) {
|
||||
for (int16_t i = prev_index; i <= index; i++) {
|
||||
float x = (float)(i - prev_index) / (index - prev_index);
|
||||
float y = 1.0f - x;
|
||||
|
||||
int16_t new_r = prev_r * y + r * x;
|
||||
int16_t new_g = prev_g * y + g * x;
|
||||
int16_t new_b = prev_b * y + b * x;
|
||||
|
||||
lut[i] = ui::Color(new_r, new_g, new_b);
|
||||
}
|
||||
|
||||
prev_index = index;
|
||||
prev_r = r;
|
||||
prev_g = g;
|
||||
prev_b = b;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Belousov Oleg
|
||||
*
|
||||
* 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 __GRADIENT_H__
|
||||
#define __GRADIENT_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
extern const std::filesystem::path default_gradient_file;
|
||||
|
||||
class Gradient {
|
||||
public:
|
||||
std::array<ui::Color, 256> lut{};
|
||||
|
||||
Gradient();
|
||||
|
||||
void set_default();
|
||||
bool load_file(const std::filesystem::path& file_path);
|
||||
|
||||
private:
|
||||
int16_t prev_index = 0;
|
||||
int16_t prev_r = 0;
|
||||
int16_t prev_g = 0;
|
||||
int16_t prev_b = 0;
|
||||
|
||||
void step(int16_t index, int16_t r, int16_t g, int16_t b);
|
||||
};
|
||||
|
||||
#endif /* __GRADIENT_H__ */
|
||||
@@ -538,7 +538,6 @@ init_status_t init() {
|
||||
|
||||
set_cpu_clock_speed();
|
||||
|
||||
if (persistent_memory::config_lcd_inverted_mode()) display.set_inverted(true);
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
|
||||
@@ -41,20 +41,20 @@ namespace {
|
||||
|
||||
static constexpr std::array<baseband::AMConfig, 12> am_configs{{
|
||||
// we config here all the non COMMON parameters to each AM modulation type in RX.
|
||||
{taps_6k0_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax.
|
||||
{taps_6k0_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
|
||||
{taps_6k0_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax.
|
||||
|
||||
// below options for Waterfall zoom x 2
|
||||
{taps_6k0_narrow_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax with waterfall zoom x 2 (we need taps_6k0_narrow_decim_1 to minimize aliasing)
|
||||
{taps_6k0_narrow_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
|
||||
{taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax with waterfall zoom x 2 (we need taps_6k0_narrow_decim_1 to minimize aliasing)
|
||||
}};
|
||||
|
||||
static constexpr std::array<baseband::NBFMConfig, 3> nbfm_configs{{
|
||||
@@ -69,6 +69,11 @@ static constexpr std::array<baseband::WFMConfig, 3> wfm_configs{{
|
||||
{taps_40k_wfm_decim_0, taps_40k_wfm_decim_1},
|
||||
}};
|
||||
|
||||
static constexpr std::array<baseband::WFMAMConfig, 2> wfmam_configs{{
|
||||
{taps_16k0_decim_0, taps_80k_wfmam_decim_1},
|
||||
{taps_16k0_decim_0, taps_38k_wfmam_decim_1},
|
||||
}};
|
||||
|
||||
} /* namespace */
|
||||
|
||||
rf::Frequency ReceiverModel::target_frequency() const {
|
||||
@@ -187,6 +192,17 @@ void ReceiverModel::set_wfm_configuration(uint8_t n) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReceiverModel::wfmam_configuration() const {
|
||||
return settings_.wfmam_config_index;
|
||||
}
|
||||
|
||||
void ReceiverModel::set_wfmam_configuration(uint8_t n) {
|
||||
if (n < wfmam_configs.size()) {
|
||||
settings_.wfmam_config_index = n;
|
||||
update_modulation();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReceiverModel::squelch_level() const {
|
||||
return settings_.squelch_level;
|
||||
}
|
||||
@@ -340,6 +356,10 @@ void ReceiverModel::update_modulation() {
|
||||
update_wfm_configuration();
|
||||
break;
|
||||
|
||||
case Mode::WFMAudioAMApt:
|
||||
update_wfmam_configuration();
|
||||
break;
|
||||
|
||||
case Mode::SpectrumAnalysis:
|
||||
case Mode::Capture:
|
||||
break;
|
||||
@@ -362,6 +382,10 @@ void ReceiverModel::update_wfm_configuration() {
|
||||
wfm_configs[wfm_configuration()].apply();
|
||||
}
|
||||
|
||||
void ReceiverModel::update_wfmam_configuration() {
|
||||
wfmam_configs[wfmam_configuration()].apply(); // update with different index for Wefax.
|
||||
}
|
||||
|
||||
void ReceiverModel::update_antenna_bias() {
|
||||
if (enabled_)
|
||||
radio::set_antenna_bias(portapack::get_antenna_bias());
|
||||
|
||||
@@ -41,8 +41,9 @@ class ReceiverModel {
|
||||
NarrowbandFMAudio = 1,
|
||||
WidebandFMAudio = 2,
|
||||
SpectrumAnalysis = 3,
|
||||
AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
Capture = 5,
|
||||
AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
WFMAudioAMApt = 5, // Added to handle SAT Weather map , NOAA 137 Mhz.
|
||||
Capture = 6,
|
||||
};
|
||||
|
||||
struct settings_t {
|
||||
@@ -56,6 +57,7 @@ class ReceiverModel {
|
||||
Mode mode = Mode::NarrowbandFMAudio;
|
||||
uint8_t am_config_index = 0;
|
||||
uint8_t amfm_config_index = 0;
|
||||
uint8_t wfmam_config_index = 0;
|
||||
uint8_t nbfm_config_index = 0;
|
||||
uint8_t wfm_config_index = 0;
|
||||
uint8_t squelch_level = 80;
|
||||
@@ -98,6 +100,9 @@ class ReceiverModel {
|
||||
uint8_t wfm_configuration() const;
|
||||
void set_wfm_configuration(uint8_t n);
|
||||
|
||||
uint8_t wfmam_configuration() const;
|
||||
void set_wfmam_configuration(uint8_t n);
|
||||
|
||||
uint8_t squelch_level() const;
|
||||
void set_squelch_level(uint8_t v);
|
||||
|
||||
@@ -151,6 +156,7 @@ class ReceiverModel {
|
||||
void update_amfm_configuration();
|
||||
void update_nbfm_configuration();
|
||||
void update_wfm_configuration();
|
||||
void update_wfmam_configuration();
|
||||
|
||||
void update_antenna_bias();
|
||||
void update_headphone_volume();
|
||||
|
||||
@@ -280,265 +280,6 @@ const std::array<ui::Color, 256> spectrum_rgb2_lut{{
|
||||
{132, 0, 0},
|
||||
}};
|
||||
|
||||
const std::array<ui::Color, 256> spectrum_rgb3_lut{{
|
||||
{0, 0, 0},
|
||||
{0, 0, 3},
|
||||
{0, 0, 6},
|
||||
{0, 0, 9},
|
||||
{0, 0, 12},
|
||||
{0, 0, 15},
|
||||
{0, 0, 18},
|
||||
{0, 0, 21},
|
||||
{0, 0, 24},
|
||||
{0, 0, 27},
|
||||
{0, 0, 30},
|
||||
{0, 0, 33},
|
||||
{0, 0, 36},
|
||||
{0, 0, 39},
|
||||
{0, 0, 42},
|
||||
{0, 0, 45},
|
||||
{0, 0, 48},
|
||||
{0, 0, 51},
|
||||
{0, 0, 54},
|
||||
{0, 0, 57},
|
||||
{0, 0, 60},
|
||||
{0, 0, 63},
|
||||
{0, 0, 66},
|
||||
{0, 0, 69},
|
||||
{0, 0, 72},
|
||||
{0, 0, 75},
|
||||
{0, 0, 78},
|
||||
{0, 0, 81},
|
||||
{0, 0, 84},
|
||||
{0, 0, 87},
|
||||
{0, 0, 90},
|
||||
{0, 0, 93},
|
||||
{0, 0, 96},
|
||||
{0, 0, 99},
|
||||
{0, 0, 102},
|
||||
{0, 0, 105},
|
||||
{0, 0, 108},
|
||||
{0, 0, 111},
|
||||
{0, 0, 114},
|
||||
{0, 0, 117},
|
||||
{0, 0, 120},
|
||||
{0, 0, 123},
|
||||
{0, 0, 126},
|
||||
{0, 0, 129},
|
||||
{0, 0, 132},
|
||||
{0, 0, 135},
|
||||
{0, 0, 138},
|
||||
{0, 0, 141},
|
||||
{0, 0, 144},
|
||||
{0, 0, 147},
|
||||
{0, 0, 150},
|
||||
{0, 0, 153},
|
||||
{0, 0, 156},
|
||||
{0, 0, 159},
|
||||
{0, 0, 162},
|
||||
{0, 0, 165},
|
||||
{0, 0, 168},
|
||||
{0, 0, 171},
|
||||
{0, 0, 174},
|
||||
{0, 0, 177},
|
||||
{0, 0, 180},
|
||||
{0, 0, 183},
|
||||
{0, 0, 186},
|
||||
{0, 0, 189},
|
||||
{0, 0, 192},
|
||||
{0, 0, 195},
|
||||
{0, 0, 198},
|
||||
{0, 0, 201},
|
||||
{0, 0, 204},
|
||||
{0, 0, 207},
|
||||
{0, 0, 210},
|
||||
{0, 0, 213},
|
||||
{0, 0, 216},
|
||||
{0, 0, 219},
|
||||
{0, 0, 222},
|
||||
{0, 0, 225},
|
||||
{0, 0, 228},
|
||||
{0, 0, 231},
|
||||
{0, 0, 234},
|
||||
{0, 0, 237},
|
||||
{0, 0, 240},
|
||||
{0, 0, 243},
|
||||
{0, 0, 246},
|
||||
{0, 0, 249},
|
||||
{0, 0, 252},
|
||||
{0, 0, 255},
|
||||
{0, 3, 252},
|
||||
{0, 6, 249},
|
||||
{0, 9, 246},
|
||||
{0, 12, 243},
|
||||
{0, 15, 240},
|
||||
{0, 18, 237},
|
||||
{0, 21, 234},
|
||||
{0, 24, 231},
|
||||
{0, 27, 228},
|
||||
{0, 30, 225},
|
||||
{0, 33, 222},
|
||||
{0, 36, 219},
|
||||
{0, 39, 216},
|
||||
{0, 42, 213},
|
||||
{0, 45, 210},
|
||||
{0, 48, 207},
|
||||
{0, 51, 204},
|
||||
{0, 54, 201},
|
||||
{0, 57, 198},
|
||||
{0, 60, 195},
|
||||
{0, 63, 192},
|
||||
{0, 66, 189},
|
||||
{0, 69, 186},
|
||||
{0, 72, 183},
|
||||
{0, 75, 180},
|
||||
{0, 78, 177},
|
||||
{0, 81, 174},
|
||||
{0, 84, 171},
|
||||
{0, 87, 168},
|
||||
{0, 90, 165},
|
||||
{0, 93, 162},
|
||||
{0, 96, 159},
|
||||
{0, 99, 156},
|
||||
{0, 102, 153},
|
||||
{0, 105, 150},
|
||||
{0, 108, 147},
|
||||
{0, 111, 144},
|
||||
{0, 114, 141},
|
||||
{0, 117, 138},
|
||||
{0, 120, 135},
|
||||
{0, 123, 132},
|
||||
{0, 126, 129},
|
||||
{0, 129, 126},
|
||||
{0, 132, 123},
|
||||
{0, 135, 120},
|
||||
{0, 138, 117},
|
||||
{0, 141, 114},
|
||||
{0, 144, 111},
|
||||
{0, 147, 108},
|
||||
{0, 150, 105},
|
||||
{0, 153, 102},
|
||||
{0, 156, 99},
|
||||
{0, 159, 96},
|
||||
{0, 162, 93},
|
||||
{0, 165, 90},
|
||||
{0, 168, 87},
|
||||
{0, 171, 84},
|
||||
{0, 174, 81},
|
||||
{0, 177, 78},
|
||||
{0, 180, 75},
|
||||
{0, 183, 72},
|
||||
{0, 186, 69},
|
||||
{0, 189, 66},
|
||||
{0, 192, 63},
|
||||
{0, 195, 60},
|
||||
{0, 198, 57},
|
||||
{0, 201, 54},
|
||||
{0, 204, 51},
|
||||
{0, 207, 48},
|
||||
{0, 210, 45},
|
||||
{0, 213, 42},
|
||||
{0, 216, 39},
|
||||
{0, 219, 36},
|
||||
{0, 222, 33},
|
||||
{0, 225, 30},
|
||||
{0, 228, 27},
|
||||
{0, 231, 24},
|
||||
{0, 234, 21},
|
||||
{0, 237, 18},
|
||||
{0, 240, 15},
|
||||
{0, 243, 12},
|
||||
{0, 246, 9},
|
||||
{0, 249, 6},
|
||||
{0, 252, 3},
|
||||
{0, 255, 0},
|
||||
{3, 252, 0},
|
||||
{6, 249, 0},
|
||||
{9, 246, 0},
|
||||
{12, 243, 0},
|
||||
{15, 240, 0},
|
||||
{18, 237, 0},
|
||||
{21, 234, 0},
|
||||
{24, 231, 0},
|
||||
{27, 228, 0},
|
||||
{30, 225, 0},
|
||||
{33, 222, 0},
|
||||
{36, 219, 0},
|
||||
{39, 216, 0},
|
||||
{42, 213, 0},
|
||||
{45, 210, 0},
|
||||
{48, 207, 0},
|
||||
{51, 204, 0},
|
||||
{54, 201, 0},
|
||||
{57, 198, 0},
|
||||
{60, 195, 0},
|
||||
{63, 192, 0},
|
||||
{66, 189, 0},
|
||||
{69, 186, 0},
|
||||
{72, 183, 0},
|
||||
{75, 180, 0},
|
||||
{78, 177, 0},
|
||||
{81, 174, 0},
|
||||
{84, 171, 0},
|
||||
{87, 168, 0},
|
||||
{90, 165, 0},
|
||||
{93, 162, 0},
|
||||
{96, 159, 0},
|
||||
{99, 156, 0},
|
||||
{102, 153, 0},
|
||||
{105, 150, 0},
|
||||
{108, 147, 0},
|
||||
{111, 144, 0},
|
||||
{114, 141, 0},
|
||||
{117, 138, 0},
|
||||
{120, 135, 0},
|
||||
{123, 132, 0},
|
||||
{126, 129, 0},
|
||||
{129, 126, 0},
|
||||
{132, 123, 0},
|
||||
{135, 120, 0},
|
||||
{138, 117, 0},
|
||||
{141, 114, 0},
|
||||
{144, 111, 0},
|
||||
{147, 108, 0},
|
||||
{150, 105, 0},
|
||||
{153, 102, 0},
|
||||
{156, 99, 0},
|
||||
{159, 96, 0},
|
||||
{162, 93, 0},
|
||||
{165, 90, 0},
|
||||
{168, 87, 0},
|
||||
{171, 84, 0},
|
||||
{174, 81, 0},
|
||||
{177, 78, 0},
|
||||
{180, 75, 0},
|
||||
{183, 72, 0},
|
||||
{186, 69, 0},
|
||||
{189, 66, 0},
|
||||
{192, 63, 0},
|
||||
{195, 60, 0},
|
||||
{198, 57, 0},
|
||||
{201, 54, 0},
|
||||
{204, 51, 0},
|
||||
{207, 48, 0},
|
||||
{210, 45, 0},
|
||||
{213, 42, 0},
|
||||
{216, 39, 0},
|
||||
{219, 36, 0},
|
||||
{222, 33, 0},
|
||||
{225, 30, 0},
|
||||
{228, 27, 0},
|
||||
{231, 24, 0},
|
||||
{234, 21, 0},
|
||||
{237, 18, 0},
|
||||
{240, 15, 0},
|
||||
{243, 12, 0},
|
||||
{246, 9, 0},
|
||||
{249, 6, 0},
|
||||
{252, 3, 0},
|
||||
{255, 0, 0},
|
||||
}};
|
||||
|
||||
const std::array<ui::Color, 256> spectrum_rgb4_lut{{
|
||||
{0, 0, 0},
|
||||
{1, 1, 1},
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <array>
|
||||
|
||||
extern const std::array<ui::Color, 256> spectrum_rgb2_lut;
|
||||
extern const std::array<ui::Color, 256> spectrum_rgb3_lut;
|
||||
extern const std::array<ui::Color, 256> spectrum_rgb4_lut;
|
||||
|
||||
#endif /*__SPECTRUM_COLOR_LUT_H__*/
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "irq_controls.hpp"
|
||||
#include "rf_path.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -37,6 +38,10 @@
|
||||
|
||||
#define MAX_UFREQ 7200000000 // maximum usable frequency
|
||||
|
||||
using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
extern options_db_t freqman_steps;
|
||||
|
||||
namespace ui {
|
||||
|
||||
class FrequencyField : public Widget {
|
||||
@@ -263,22 +268,12 @@ class FrequencyStepView : public OptionsField {
|
||||
: OptionsField{
|
||||
parent_pos,
|
||||
5,
|
||||
{
|
||||
{" 10", 10}, /* Fine tuning SSB voice pitch,in HF and QO-100 sat #669 */
|
||||
{" 50", 50}, /* added 50Hz/10Hz,but we do not increase GUI digit decimal */
|
||||
{" 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},
|
||||
{"100k ", 100000},
|
||||
{" 1M ", 1000000},
|
||||
{" 10M ", 10000000},
|
||||
}} {
|
||||
{}} {
|
||||
options_t options;
|
||||
for (const auto& step : freqman_steps) {
|
||||
options.emplace_back(step.first, step.second);
|
||||
}
|
||||
set_options(options);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyleft Mr. Robot 2025
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -21,8 +22,6 @@
|
||||
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#include "spectrum_color_lut.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
@@ -102,6 +101,15 @@ void FrequencyScale::set_channel_filter(
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyScale::set_cursor_position(const int32_t position) {
|
||||
cursor_position = position;
|
||||
|
||||
cursor_position = std::min<int32_t>(cursor_position, 119);
|
||||
cursor_position = std::max<int32_t>(cursor_position, -120);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void FrequencyScale::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
@@ -115,14 +123,12 @@ void FrequencyScale::paint(Painter& painter) {
|
||||
draw_filter_ranges(painter, r);
|
||||
draw_frequency_ticks(painter, r);
|
||||
|
||||
if (_blink) {
|
||||
const Rect r_cursor{
|
||||
118 + cursor_position, r.bottom() - filter_band_height,
|
||||
5, filter_band_height};
|
||||
painter.fill_rectangle(
|
||||
r_cursor,
|
||||
Color::red());
|
||||
}
|
||||
const Rect r_cursor{
|
||||
118 + cursor_position, r.bottom() - filter_band_height,
|
||||
5, filter_band_height};
|
||||
painter.fill_rectangle(
|
||||
r_cursor,
|
||||
Color::red());
|
||||
}
|
||||
|
||||
void FrequencyScale::clear() {
|
||||
@@ -206,16 +212,10 @@ void FrequencyScale::draw_filter_ranges(Painter& painter, const Rect r) {
|
||||
}
|
||||
|
||||
void FrequencyScale::on_focus() {
|
||||
_blink = true;
|
||||
on_tick_second();
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
this->on_tick_second();
|
||||
};
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void FrequencyScale::on_blur() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
_blink = false;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -235,6 +235,7 @@ bool FrequencyScale::on_key(const KeyEvent key) {
|
||||
if (on_select) {
|
||||
on_select((cursor_position * spectrum_sampling_rate) / 240);
|
||||
cursor_position = 0;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -242,9 +243,13 @@ bool FrequencyScale::on_key(const KeyEvent key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FrequencyScale::on_tick_second() {
|
||||
set_dirty();
|
||||
_blink = !_blink;
|
||||
bool FrequencyScale::on_touch(const TouchEvent touch) {
|
||||
if (touch.type == TouchEvent::Type::Start) {
|
||||
if (on_select) {
|
||||
on_select((touch.point.x() * spectrum_sampling_rate) / 240);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* WaterfallWidget *********************************************************/
|
||||
@@ -271,12 +276,12 @@ void WaterfallWidget::on_channel_spectrum(
|
||||
|
||||
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]];
|
||||
const auto pixel_color = gradient.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]];
|
||||
const auto pixel_color = gradient.lut[spectrum.db[i - 120]];
|
||||
pixel_row[i] = pixel_color;
|
||||
}
|
||||
|
||||
@@ -287,6 +292,15 @@ void WaterfallWidget::on_channel_spectrum(
|
||||
pixel_row);
|
||||
}
|
||||
|
||||
bool WaterfallWidget::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start) {
|
||||
if (on_touch_select) {
|
||||
on_touch_select(event.point.x(), event.point.y());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaterfallWidget::clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
@@ -305,6 +319,22 @@ WaterfallView::WaterfallView(const bool cursor) {
|
||||
frequency_scale.on_select = [this](int32_t offset) {
|
||||
if (on_select) on_select(offset);
|
||||
};
|
||||
|
||||
waterfall_widget.on_touch_select = [this](int32_t x, int32_t y) {
|
||||
if (y > screen_height - screen_height * 0.1) return; // prevent ghost touch
|
||||
|
||||
frequency_scale.focus(); // focus on frequency scale to show cursor
|
||||
|
||||
if (sampling_rate) {
|
||||
// screen x to frequency scale x, NB we need two widgets align
|
||||
int32_t cursor_position = x - (screen_width / 2);
|
||||
frequency_scale.set_cursor_position(cursor_position);
|
||||
}
|
||||
};
|
||||
|
||||
if (!waterfall_widget.gradient.load_file(default_gradient_file)) {
|
||||
waterfall_widget.gradient.set_default();
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyleft Mr. Robot 2025
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -24,6 +25,7 @@
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "gradient.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
@@ -78,20 +80,18 @@ class FrequencyScale : public Widget {
|
||||
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_touch(const TouchEvent touch) override;
|
||||
|
||||
void set_spectrum_sampling_rate(const int new_sampling_rate);
|
||||
void set_channel_filter(const int low_frequency, const int high_frequency, const int transition);
|
||||
void set_cursor_position(const int32_t position);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
static constexpr int filter_band_height = 4;
|
||||
|
||||
void on_tick_second();
|
||||
|
||||
bool _blink{false};
|
||||
int32_t cursor_position{0};
|
||||
SignalToken signal_token_tick_second{};
|
||||
int spectrum_sampling_rate{0};
|
||||
const int spectrum_bins = std::tuple_size<decltype(ChannelSpectrum::db)>::value;
|
||||
int channel_filter_low_frequency{0};
|
||||
@@ -111,9 +111,14 @@ class FrequencyScale : public Widget {
|
||||
|
||||
class WaterfallWidget : public Widget {
|
||||
public:
|
||||
std::function<void(int32_t offset, int32_t y)> on_touch_select{};
|
||||
|
||||
Gradient gradient{};
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
void paint(Painter&) override {}
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
|
||||
|
||||
@@ -155,6 +155,9 @@ TransmitterView::TransmitterView(
|
||||
|
||||
field_bw.on_change = [this](int32_t v) {
|
||||
on_channel_bandwidth_changed(v * 1000);
|
||||
if (on_bandwidth_changed) {
|
||||
on_bandwidth_changed();
|
||||
}
|
||||
};
|
||||
field_bw.set_value(channel_bandwidth);
|
||||
}
|
||||
@@ -257,4 +260,4 @@ void TransmitterView2::update_gainlevel_styles() {
|
||||
field_amp.set_style(style);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
@@ -50,6 +50,10 @@ class TransmitterView : public View {
|
||||
std::function<void(void)> on_edit_frequency{};
|
||||
std::function<void(void)> on_start{};
|
||||
std::function<void(void)> on_stop{};
|
||||
std::function<void(void)> on_bandwidth_changed{};
|
||||
// TODO: this is a workaround because in the message system,
|
||||
// we change bw by different message for different m4 bb, so have to callback
|
||||
// and change in the instance(for instance SigGen app) with calling the instance's bb opration func
|
||||
|
||||
TransmitterView(const Coord y, const uint64_t frequency_step, const uint32_t channel_bandwidth, const bool lock);
|
||||
TransmitterView(
|
||||
@@ -156,4 +160,4 @@ class TransmitterView2 : public View {
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_TRANSMITTER_H__*/
|
||||
#endif /*__UI_TRANSMITTER_H__*/
|
||||
@@ -117,10 +117,10 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{nullptr, "Tranceiver", HOME, Color::cyan(), &bitmap_icon_tranceivers, new ViewFactory<TranceiversMenuView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
|
||||
@@ -147,6 +147,8 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
{"signalgen", "SignalGen", TX, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
/* TRX ********************************************************************/
|
||||
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
@@ -380,7 +382,7 @@ void SystemStatusView::refresh() {
|
||||
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker);
|
||||
|
||||
if (!pmem::ui_hide_fake_brightness() && !pmem::config_lcd_inverted_mode()) status_icons.add(&button_fake_brightness);
|
||||
if (!pmem::ui_hide_fake_brightness()) status_icons.add(&button_fake_brightness);
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
batt_was_inited = true;
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
@@ -414,7 +416,7 @@ void SystemStatusView::refresh() {
|
||||
button_converter.set_foreground(pmem::config_converter() ? Theme::getInstance()->fg_red->foreground : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
// Fake Brightness
|
||||
button_fake_brightness.set_foreground((pmem::apply_fake_brightness() & (!pmem::config_lcd_inverted_mode())) ? *Theme::getInstance()->status_active : Theme::getInstance()->fg_light->foreground);
|
||||
button_fake_brightness.set_foreground(pmem::apply_fake_brightness() ? *Theme::getInstance()->status_active : Theme::getInstance()->fg_light->foreground);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
@@ -841,6 +843,22 @@ void TransmittersMenuView::on_populate() {
|
||||
add_external_items(nav_, app_location_t::TX, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* TranceiversMenuView **************************************************/
|
||||
|
||||
TranceiversMenuView::TranceiversMenuView(NavigationView& nav)
|
||||
: nav_(nav) {}
|
||||
|
||||
void TranceiversMenuView::on_populate() {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
add_apps(nav_, *this, TRX);
|
||||
// add_external_items(nav_, app_location_t::TRX, *this, return_icon ? 1 : 0);
|
||||
// this folder doesn't have external apps, comment to prevent pop the err msg.
|
||||
// NB: when has external app someday, uncomment this.
|
||||
}
|
||||
|
||||
/* UtilitiesMenuView *****************************************************/
|
||||
|
||||
UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav)
|
||||
|
||||
@@ -379,6 +379,16 @@ class TransmittersMenuView : public BtnGridView {
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class TranceiversMenuView : public BtnGridView {
|
||||
public:
|
||||
TranceiversMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Tranceiver"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class UtilitiesMenuView : public BtnGridView {
|
||||
public:
|
||||
UtilitiesMenuView(NavigationView& nav);
|
||||
|
||||
@@ -691,6 +691,9 @@ static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element)
|
||||
case TX:
|
||||
chprintf(chp, "[TX]\r\n");
|
||||
break;
|
||||
case TRX:
|
||||
chprintf(chp, "[TRX]\r\n");
|
||||
break;
|
||||
case UTILITIES:
|
||||
chprintf(chp, "[UTIL]\r\n");
|
||||
break;
|
||||
@@ -715,6 +718,9 @@ static void printAppInfo(BaseSequentialStream* chp, const ui::AppInfo& element)
|
||||
case TX:
|
||||
chprintf(chp, "[TX]\r\n");
|
||||
break;
|
||||
case TRX:
|
||||
chprintf(chp, "[TRX]\r\n");
|
||||
break;
|
||||
case UTILITIES:
|
||||
chprintf(chp, "[UTIL]\r\n");
|
||||
break;
|
||||
|
||||
@@ -676,6 +676,14 @@ set(MODE_CPPSRC
|
||||
DeclareTargets(PWFX wefaxrx)
|
||||
|
||||
|
||||
### NoaaApt Rx
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_noaaapt_rx.cpp
|
||||
)
|
||||
DeclareTargets(PNOA noaaapt_rx)
|
||||
|
||||
|
||||
### HackRF "factory" firmware
|
||||
|
||||
add_custom_command(
|
||||
|
||||
@@ -52,6 +52,31 @@ void AudioOutput::write_unprocessed(const buffer_s16_t& audio) {
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::apt_write(const buffer_s16_t& audio) {
|
||||
std::array<float, 32> audio_f;
|
||||
for (size_t i = 0; i < audio.count; i++) {
|
||||
cur = audio.p[i];
|
||||
cur2 = cur * cur;
|
||||
mag_am = sqrtf(prev2 + cur2 - (2 * prev * cur * cos_theta)) / sin_theta;
|
||||
audio_f[i] = mag_am * ki; // normalize.
|
||||
prev = cur;
|
||||
prev2 = cur2;
|
||||
}
|
||||
write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate});
|
||||
}
|
||||
|
||||
void AudioOutput::apt_write(const buffer_s16_t& audio, std::array<float, 32>& audio_f) {
|
||||
for (size_t i = 0; i < audio.count; i++) {
|
||||
cur = audio.p[i];
|
||||
cur2 = cur * cur;
|
||||
mag_am = sqrtf(prev2 + cur2 - (2 * prev * cur * cos_theta)) / sin_theta;
|
||||
audio_f[i] = mag_am * ki; // normalize.
|
||||
prev = cur;
|
||||
prev2 = cur2;
|
||||
}
|
||||
write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate});
|
||||
}
|
||||
|
||||
void AudioOutput::write(const buffer_s16_t& audio) {
|
||||
std::array<float, 32> audio_f;
|
||||
for (size_t i = 0; i < audio.count; i++) {
|
||||
@@ -72,8 +97,8 @@ void AudioOutput::on_block(const buffer_f32_t& audio) {
|
||||
if (do_processing) {
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
|
||||
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef"
|
||||
deemph.execute_in_place(audio);
|
||||
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef" and notch for WFMAM (NOAA)
|
||||
deemph.execute_in_place(audio); // IIRBiquadFilter name is "deemph", but we will call LPF de-emphasis or other LPF for WFAM (NOAA).
|
||||
|
||||
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
|
||||
audio_present = (audio_present_history != 0);
|
||||
|
||||
@@ -46,6 +46,8 @@ class AudioOutput {
|
||||
const float squelch_threshold = 0.0f);
|
||||
|
||||
void write_unprocessed(const buffer_s16_t& audio);
|
||||
void apt_write(const buffer_s16_t& audio);
|
||||
void apt_write(const buffer_s16_t& audio, std::array<float, 32>& audio_f);
|
||||
void write(const buffer_s16_t& audio);
|
||||
void write(const buffer_f32_t& audio);
|
||||
|
||||
@@ -58,6 +60,10 @@ class AudioOutput {
|
||||
private:
|
||||
static constexpr float k = 32768.0f;
|
||||
static constexpr float ki = 1.0f / k;
|
||||
static constexpr float cos_theta = 0.30901699437494742410f;
|
||||
static constexpr float sin_theta = 0.95105651629515357212f;
|
||||
|
||||
float cur = 0.0f, cur2 = 0.0f, prev = 0.0f, prev2 = 0.0f, mag_am = 0.0f;
|
||||
|
||||
BlockDecimator<int16_t, 32> block_buffer_s16{1};
|
||||
BlockDecimator<float, 32> block_buffer{1};
|
||||
|
||||
@@ -73,13 +73,8 @@ void ADSBRXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
// Perform additional check on the first byte.
|
||||
if (bit_count == 8) {
|
||||
// Abandon all frames that aren't DF17 or DF18 extended squitters.
|
||||
uint8_t df = (byte >> 3);
|
||||
if (df != 17 && df != 18) {
|
||||
decoding = false;
|
||||
bit = (prev_mag > mag) ? 1 : 0;
|
||||
frame.clear();
|
||||
}
|
||||
// try to receive all frames instead
|
||||
msg_len = (byte & 0x80) ? 112 : 56; // determine message len by type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class ADSBRXProcessor : public BasebandProcessor {
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2'000'000;
|
||||
static constexpr size_t msg_len = 112;
|
||||
size_t msg_len{112};
|
||||
|
||||
ADSBFrame frame{};
|
||||
bool configured{false};
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Brumi, HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "proc_noaaapt_rx.hpp"
|
||||
#include "sine_table_int8.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "audio_dma.hpp"
|
||||
#include "math.h"
|
||||
#include "event_m4.hpp"
|
||||
#include "fxpt_atan2.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#define NOAAAPT_PX_SIZE 2080.0
|
||||
|
||||
// updates the per pixel timers
|
||||
void NoaaAptRx::update_params() {
|
||||
// TODO HTOTOO
|
||||
// 2080 px / line with chan a,b + sync + telemetry
|
||||
pxRem = (double)12000.0 / NOAAAPT_PX_SIZE / 2.0;
|
||||
samples_per_pixel = pxRem;
|
||||
pxRem -= samples_per_pixel;
|
||||
pxRoll = 0;
|
||||
status_message.state = 0;
|
||||
shared_memory.application_queue.push(status_message);
|
||||
}
|
||||
|
||||
void NoaaAptRx::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) {
|
||||
return;
|
||||
}
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto channel = decim_1.execute(decim_0_out, dst_buffer);
|
||||
// TODO: Feed channel_stats post-decimation data?
|
||||
feed_channel_stats(channel);
|
||||
|
||||
/* spectrum_samples += channel.count;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.feed(channel, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
|
||||
}
|
||||
*/
|
||||
/* 96kHz complex<int16_t>[64] for wfmam NOAA
|
||||
* -> FM demodulation
|
||||
* -> 96kHz int16_t[64] */
|
||||
auto audio_oversampled = demod.execute(channel, work_audio_buffer); // fs 384khz wfm , 96khz wfmam for NOAA
|
||||
/* 96kHz int16_t[64] for wfam
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 48kHz int16_t[32] */
|
||||
auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer);
|
||||
/* 48kHz int16_t[32] for wfman
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 24kHz int16_t[16] */
|
||||
auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer);
|
||||
/* 24kHz int16_t[16] for wfmam
|
||||
* -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop, gain of 1
|
||||
* -> 12kHz int16_t[8] */
|
||||
auto audio = audio_filter.execute(audio_2fs, work_audio_buffer);
|
||||
/* -> 12kHz int16_t[8] for wfmam , */
|
||||
std::array<float, 32> audio_f;
|
||||
audio_output.apt_write(audio, audio_f); // we are in added wfmam (noaa), decim_1.decimation_factor == 8
|
||||
|
||||
for (size_t c = 0; c < audio.count; c++) {
|
||||
if (status_message.state == 0 && false) { // disabled this due to NIY
|
||||
// first look for the sync!
|
||||
|
||||
} else {
|
||||
cnt++;
|
||||
if (cnt >= (samples_per_pixel + (uint32_t)pxRoll)) { // got a pixel
|
||||
cnt = 0;
|
||||
if (pxRoll >= 1) pxRoll -= 1.0;
|
||||
pxRoll += pxRem;
|
||||
|
||||
if (image_message.cnt < 400) {
|
||||
if (audio_f[c] >= 1) {
|
||||
image_message.image[image_message.cnt++] = 255;
|
||||
} else if (audio_f[c] <= 0) {
|
||||
image_message.image[image_message.cnt++] = 0;
|
||||
} else {
|
||||
image_message.image[image_message.cnt++] = (audio_f[c]) * 255;
|
||||
}
|
||||
}
|
||||
if (image_message.cnt >= 399) {
|
||||
shared_memory.application_queue.push(image_message);
|
||||
image_message.cnt = 0;
|
||||
if (status_message.state != 2) {
|
||||
status_message.state = 2;
|
||||
shared_memory.application_queue.push(status_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NoaaAptRx::on_message(const Message* const message) {
|
||||
switch (message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
// channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
case Message::ID::NoaaAptRxConfigure:
|
||||
configure(*reinterpret_cast<const NoaaAptRxConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
case Message::ID::CaptureConfig:
|
||||
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NoaaAptRx::configure(const NoaaAptRxConfigureMessage& message) {
|
||||
(void)message;
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
|
||||
decim_0.configure(taps_16k0_decim_0.taps);
|
||||
|
||||
// decim_1.configure(message.decim_1_filter.taps); // Original .
|
||||
// TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps .
|
||||
// Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 . (here /8)
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps); // for wfm
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(message.decim_1_filter.taps); // for wfmam
|
||||
decim_1.configure(taps_38k_wfmam_decim_1.taps); // for wfmam
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; // wfmam, decim_1.decimation_factor() = /8 ,if applied after the line, decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps);
|
||||
size_t demod_input_fs = decim_1_output_fs;
|
||||
|
||||
// spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz;
|
||||
// spectrum_samples = 0;
|
||||
|
||||
channel_filter_low_f = taps_38k_wfmam_decim_1.low_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_high_f = taps_38k_wfmam_decim_1.high_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_transition = taps_38k_wfmam_decim_1.transition_normalized * decim_1_input_fs;
|
||||
demod.configure(demod_input_fs, 17000);
|
||||
audio_filter.configure(taps_64_lp_1875_2166.taps);
|
||||
audio_output.configure(apt_audio_12k_notch_2k4_config, apt_audio_12k_lpf_2000hz_config);
|
||||
// channel_spectrum.set_decimation_factor(1);
|
||||
update_params();
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void NoaaAptRx::capture_config(const CaptureConfigMessage& message) {
|
||||
if (message.config) {
|
||||
audio_output.set_stream(std::make_unique<StreamInput>(message.config));
|
||||
} else {
|
||||
audio_output.set_stream(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
audio::dma::init_audio_out();
|
||||
EventDispatcher event_dispatcher{std::make_unique<NoaaAptRx>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Brumi, HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PROC_NOAAAPTRX_H__
|
||||
#define __PROC_NOAAAPTRX_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "audio_compressor.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class NoaaAptRx : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
void update_params();
|
||||
// todo rethink
|
||||
|
||||
uint32_t samples_per_pixel = 0;
|
||||
|
||||
// to exactly match the pixel / samples.
|
||||
double pxRem = 0; // if has remainder, it'll store it
|
||||
double pxRoll = 0; // summs remainders, so won't misalign
|
||||
|
||||
uint32_t cnt = 0; // signal counter
|
||||
uint16_t sync_cnt = 0;
|
||||
uint16_t syncnot_cnt = 0;
|
||||
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
// work_audio_buffer and dst_buffer use the same data pointer
|
||||
const buffer_s16_t work_audio_buffer{
|
||||
(int16_t*)dst.data(),
|
||||
sizeof(dst) / sizeof(int16_t)};
|
||||
|
||||
std::array<complex16_t, 64> complex_audio{};
|
||||
const buffer_c16_t complex_audio_buffer{
|
||||
complex_audio.data(),
|
||||
complex_audio.size()};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
|
||||
// dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam
|
||||
|
||||
// decim_1 will handle different types of FIR filters depending on selection.
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||
|
||||
// dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; // For FMAM
|
||||
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
int32_t channel_filter_transition = 0;
|
||||
|
||||
dsp::demodulate::FM demod{};
|
||||
dsp::decimate::DecimateBy2CIC4Real audio_dec_1{};
|
||||
dsp::decimate::DecimateBy2CIC4Real audio_dec_2{};
|
||||
dsp::decimate::FIR64AndDecimateBy2Real audio_filter{};
|
||||
|
||||
AudioOutput audio_output{};
|
||||
|
||||
// For fs=96kHz FFT streaming
|
||||
BlockDecimator<complex16_t, 256> audio_spectrum_decimator{1};
|
||||
std::array<std::complex<float>, 256> audio_spectrum{};
|
||||
uint32_t audio_spectrum_timer{0};
|
||||
enum AudioSpectrumState {
|
||||
IDLE = 0,
|
||||
FEED,
|
||||
FFT
|
||||
};
|
||||
AudioSpectrumState audio_spectrum_state{IDLE};
|
||||
AudioSpectrum spectrum{};
|
||||
uint32_t fft_step{0};
|
||||
|
||||
// SpectrumCollector channel_spectrum{};
|
||||
// size_t spectrum_interval_samples = 0;
|
||||
// size_t spectrum_samples = 0;
|
||||
|
||||
bool configured{false};
|
||||
|
||||
void configure(const NoaaAptRxConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
|
||||
NoaaAptRxStatusDataMessage status_message{0};
|
||||
NoaaAptRxImageDataMessage image_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_NOAAAPTRX_H__*/
|
||||
@@ -161,7 +161,7 @@ void WeFaxRx::configure(const WeFaxRxConfigureMessage& message) {
|
||||
channel_filter_high_f = taps_2k6_usb_wefax_channel.high_frequency_normalized * channel_filter_input_fs;
|
||||
channel_filter_transition = taps_2k6_usb_wefax_channel.transition_normalized * channel_filter_input_fs;
|
||||
channel_spectrum.set_decimation_factor(1.0f);
|
||||
audio_output.configure(audio_12k_lpf_1500hz_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
|
||||
audio_output.configure(apt_audio_12k_lpf_1500hz_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
|
||||
|
||||
lpm = message.lpm;
|
||||
ioc_mode = message.ioc;
|
||||
|
||||
@@ -47,23 +47,37 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
channel_spectrum.feed(channel, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
|
||||
}
|
||||
|
||||
/* 384kHz complex<int16_t>[256]
|
||||
/* 384kHz complex<int16_t>[256] for wfm
|
||||
* -> FM demodulation
|
||||
* -> 384kHz int16_t[256] */
|
||||
/* TODO: To improve adjacent channel rejection, implement complex channel filter:
|
||||
* pass < +/- 100kHz, stop > +/- 200kHz
|
||||
*/
|
||||
|
||||
auto audio_oversampled = demod.execute(channel, work_audio_buffer);
|
||||
/* 96kHz complex<int16_t>[64] for wfmam NOAA
|
||||
* -> FM demodulation
|
||||
* -> 96kHz int16_t[64] */
|
||||
|
||||
/* 384kHz int16_t[256]
|
||||
auto audio_oversampled = demod.execute(channel, work_audio_buffer); // fs 384khz wfm , 96khz wfmam for NOAA
|
||||
|
||||
/* 384kHz int16_t[256] for wfm
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 192kHz int16_t[128] */
|
||||
|
||||
/* 96kHz int16_t[64] for wfam
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 48kHz int16_t[32] */
|
||||
|
||||
auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer);
|
||||
|
||||
/* 192kHz int16_t[128]
|
||||
/* 192kHz int16_t[128] for wfm
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 96kHz int16_t[64] */
|
||||
|
||||
/* 48kHz int16_t[32] for wfman
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 24kHz int16_t[16] */
|
||||
|
||||
auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer);
|
||||
|
||||
// Input: 96kHz int16_t[64]
|
||||
@@ -118,13 +132,24 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 96kHz int16_t[64]
|
||||
/* 96kHz int16_t[64] for wfm
|
||||
* -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1
|
||||
* -> 48kHz int16_t[32] */
|
||||
|
||||
/* 24kHz int16_t[16] for wfmam
|
||||
* -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop, gain of 1
|
||||
* -> 12kHz int16_t[8] */
|
||||
|
||||
auto audio = audio_filter.execute(audio_2fs, work_audio_buffer);
|
||||
|
||||
/* -> 48kHz int16_t[32] */
|
||||
audio_output.write(audio);
|
||||
/* -> 48kHz int16_t[32] for wfm , */
|
||||
/* -> 12kHz int16_t[8] for wfmam , */
|
||||
|
||||
if (decim_1.decimation_factor() == 2) {
|
||||
audio_output.write(audio); // we are in original wfm , decim_1.decimation_factor == 2
|
||||
} else {
|
||||
audio_output.apt_write(audio); // we are in added wfmam (noaa), decim_1.decimation_factor == 8
|
||||
}
|
||||
}
|
||||
|
||||
void WidebandFMAudio::post_message(const buffer_c16_t& data) {
|
||||
@@ -142,7 +167,11 @@ void WidebandFMAudio::on_message(const Message* const message) {
|
||||
break;
|
||||
|
||||
case Message::ID::WFMConfigure:
|
||||
configure(*reinterpret_cast<const WFMConfigureMessage*>(message));
|
||||
configure_wfm(*reinterpret_cast<const WFMConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
case Message::ID::WFMAMConfigure:
|
||||
configure_wfmam(*reinterpret_cast<const WFMAMConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
case Message::ID::CaptureConfig:
|
||||
@@ -154,20 +183,56 @@ void WidebandFMAudio::on_message(const Message* const message) {
|
||||
}
|
||||
}
|
||||
|
||||
void WidebandFMAudio::configure(const WFMConfigureMessage& message) {
|
||||
void WidebandFMAudio::configure_wfm(const WFMConfigureMessage& message) {
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
constexpr size_t demod_input_fs = decim_1_output_fs;
|
||||
decim_0.configure(message.decim_0_filter.taps);
|
||||
// decim_1.configure(message.decim_1_filter.taps); // Original .
|
||||
|
||||
// TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps .
|
||||
// Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 (here /2)
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps); // for wfm
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(taps_84k_wfmam_decim_1.taps); // for wfmam
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps); // for wfm
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor(); // wfm , decim_1.decimation_factor() = /2 , if applied after the line : decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps);
|
||||
size_t demod_input_fs = decim_1_output_fs;
|
||||
|
||||
spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz;
|
||||
spectrum_samples = 0;
|
||||
|
||||
channel_filter_low_f = message.decim_1_filter.low_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_high_f = message.decim_1_filter.high_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_transition = message.decim_1_filter.transition_normalized * decim_1_input_fs;
|
||||
demod.configure(demod_input_fs, message.deviation);
|
||||
audio_filter.configure(message.audio_filter.taps);
|
||||
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config);
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void WidebandFMAudio::configure_wfmam(const WFMAMConfigureMessage& message) {
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
|
||||
decim_0.configure(message.decim_0_filter.taps);
|
||||
decim_1.configure(message.decim_1_filter.taps);
|
||||
|
||||
// decim_1.configure(message.decim_1_filter.taps); // Original .
|
||||
// TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps .
|
||||
// Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 . (here /8)
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps); // for wfm
|
||||
// decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(message.decim_1_filter.taps); // for wfmam
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(message.decim_1_filter.taps); // for wfmam
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor(); // wfmam, decim_1.decimation_factor() = /8 ,if applied after the line, decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(message.decim_1_filter.taps);
|
||||
size_t demod_input_fs = decim_1_output_fs;
|
||||
|
||||
spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz;
|
||||
spectrum_samples = 0;
|
||||
|
||||
channel_filter_low_f = message.decim_1_filter.low_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_high_f = message.decim_1_filter.high_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_transition = message.decim_1_filter.transition_normalized * decim_1_input_fs;
|
||||
|
||||
@@ -35,6 +35,46 @@
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
template <typename... Args>
|
||||
class MultiDecimator {
|
||||
public:
|
||||
/* Dispatches to the underlying type's execute. */
|
||||
template <typename Source, typename Destination>
|
||||
Destination execute(
|
||||
const Source& src,
|
||||
const Destination& dst) {
|
||||
return std::visit(
|
||||
[&src, &dst](auto&& arg) -> Destination {
|
||||
return arg.execute(src, dst);
|
||||
},
|
||||
decimator_);
|
||||
}
|
||||
|
||||
size_t decimation_factor() const {
|
||||
return std::visit(
|
||||
[](auto&& arg) -> size_t {
|
||||
return arg.decimation_factor;
|
||||
},
|
||||
decimator_);
|
||||
}
|
||||
|
||||
/* Sets this decimator to a new instance of the specified decimator type.
|
||||
* NB: The instance is returned by-ref so 'configure' can easily be called. */
|
||||
template <typename Decimator>
|
||||
Decimator& set() {
|
||||
decimator_ = Decimator{};
|
||||
return std::get<Decimator>(decimator_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<Args...> decimator_{};
|
||||
};
|
||||
|
||||
class WidebandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
@@ -59,7 +99,16 @@ class WidebandFMAudio : public BasebandProcessor {
|
||||
complex_audio.size()};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
// dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam
|
||||
|
||||
// decim_1 will handle different types of FIR filters depending on selection.
|
||||
MultiDecimator<
|
||||
dsp::decimate::FIRC16xR16x16Decim2,
|
||||
dsp::decimate::FIRC16xR16x32Decim8>
|
||||
decim_1{};
|
||||
|
||||
// dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; // For FMAM
|
||||
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
int32_t channel_filter_transition = 0;
|
||||
@@ -94,7 +143,8 @@ class WidebandFMAudio : public BasebandProcessor {
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const WFMConfigureMessage& message);
|
||||
void configure_wfm(const WFMConfigureMessage& message);
|
||||
void configure_wfmam(const WFMAMConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
void post_message(const buffer_c16_t& data);
|
||||
};
|
||||
|
||||
+42
-27
@@ -282,7 +282,7 @@ adsb_pos decode_frame_pos(ADSBFrame& frame_even, ADSBFrame& frame_odd) {
|
||||
uint32_t latcprE, latcprO, loncprE, loncprO;
|
||||
float latE, latO, m, Dlon, cpr_lon_odd, cpr_lon_even, cpr_lat_odd, cpr_lat_even;
|
||||
int ni;
|
||||
adsb_pos position{false, 0, 0, 0};
|
||||
adsb_pos position{false, false, 0, 0, 0};
|
||||
|
||||
uint32_t time_even = frame_even.get_rx_timestamp();
|
||||
uint32_t time_odd = frame_odd.get_rx_timestamp();
|
||||
@@ -296,8 +296,10 @@ adsb_pos decode_frame_pos(ADSBFrame& frame_even, ADSBFrame& frame_odd) {
|
||||
raw_data = frame_data_odd;
|
||||
|
||||
// Q-bit must be present
|
||||
if (raw_data[5] & 1)
|
||||
if (raw_data[5] & 1) {
|
||||
position.altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
|
||||
position.alt_valid = true;
|
||||
}
|
||||
|
||||
// Position
|
||||
latcprE = ((frame_data_even[6] & 3) << 15) | (frame_data_even[7] << 7) | (frame_data_even[8] >> 1);
|
||||
@@ -350,7 +352,7 @@ adsb_pos decode_frame_pos(ADSBFrame& frame_even, ADSBFrame& frame_odd) {
|
||||
|
||||
if (position.longitude >= 180) position.longitude -= 360;
|
||||
|
||||
position.valid = true;
|
||||
position.pos_valid = true;
|
||||
|
||||
return position;
|
||||
}
|
||||
@@ -396,46 +398,59 @@ void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint
|
||||
|
||||
// Decoding method from dump1090
|
||||
adsb_vel decode_frame_velo(ADSBFrame& frame) {
|
||||
adsb_vel velo{false, 0, 0, 0};
|
||||
adsb_vel velo{false, SPD_GND, 0, 0, 0};
|
||||
|
||||
uint8_t* frame_data = frame.get_raw_data();
|
||||
uint8_t velo_type = frame.get_msg_sub();
|
||||
|
||||
if (velo_type >= 1 && velo_type <= 4) { // vertical rate is always present
|
||||
|
||||
velo.v_rate = (((frame_data[8] & 0x07) << 6) | ((frame_data[9] >> 2) - 1)) * 64;
|
||||
velo.v_rate = ((((frame_data[8] & 0x07) << 6) | (frame_data[9] >> 2)) - 1) * 64;
|
||||
|
||||
if ((frame_data[8] & 0x8) >> 3) velo.v_rate *= -1; // check v_rate sign
|
||||
if (frame_data[8] & 0x8) velo.v_rate *= -1; // check v_rate sign
|
||||
}
|
||||
|
||||
if (velo_type == 1 || velo_type == 2) { // Ground Speed
|
||||
int32_t raw_ew = ((frame_data[5] & 0x03) << 8) | frame_data[6];
|
||||
int32_t velo_ew = raw_ew - 1; // velocities are all offset by one (this is part of the spec)
|
||||
|
||||
int32_t raw_ns = ((frame_data[7] & 0x7f) << 3) | (frame_data[8] >> 5);
|
||||
int32_t velo_ns = raw_ns - 1;
|
||||
|
||||
if (velo_type == 2) { // supersonic indicator so multiply by 4
|
||||
velo_ew = velo_ew << 2;
|
||||
velo_ns = velo_ns << 2;
|
||||
if (raw_ew && raw_ns) { // check data available
|
||||
int32_t velo_ew = raw_ew - 1; // velocities are all offset by one (this is part of the spec)
|
||||
int32_t velo_ns = raw_ns - 1;
|
||||
|
||||
if (velo_type == 2) { // supersonic indicator so multiply by 4
|
||||
velo_ew = velo_ew << 2;
|
||||
velo_ns = velo_ns << 2;
|
||||
}
|
||||
|
||||
if (frame_data[5] & 0x04) velo_ew *= -1; // check ew direction sign
|
||||
if (frame_data[7] & 0x80) velo_ns *= -1; // check ns direction sign
|
||||
|
||||
velo.speed = fast_int_magnitude(velo_ns, velo_ew);
|
||||
|
||||
if (velo.speed) {
|
||||
// calculate heading in degrees from ew/ns velocities
|
||||
int16_t heading_temp = (int16_t)(int_atan2(velo_ew, velo_ns)); // Nearest degree
|
||||
// We don't want negative values but a 0-360 scale.
|
||||
if (heading_temp < 0) heading_temp += 360.0;
|
||||
velo.heading = (uint16_t)heading_temp;
|
||||
|
||||
velo.valid = true;
|
||||
velo.type = SPD_GND;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_data[5] & 0x04) velo_ew *= -1; // check ew direction sign
|
||||
if (frame_data[7] & 0x80) velo_ns *= -1; // check ns direction sign
|
||||
|
||||
velo.speed = fast_int_magnitude(velo_ns, velo_ew);
|
||||
|
||||
if (velo.speed) {
|
||||
// calculate heading in degrees from ew/ns velocities
|
||||
int16_t heading_temp = (int16_t)(int_atan2(velo_ew, velo_ns)); // Nearest degree
|
||||
// We don't want negative values but a 0-360 scale.
|
||||
if (heading_temp < 0) heading_temp += 360.0;
|
||||
velo.heading = (uint16_t)heading_temp;
|
||||
}
|
||||
|
||||
} else if (velo_type == 3 || velo_type == 4) { // Airspeed
|
||||
velo.valid = frame_data[5] & (1 << 2);
|
||||
velo.heading = ((((frame_data[5] & 0x03) << 8) | frame_data[6]) * 45) << 7;
|
||||
velo.heading = ((((frame_data[5] & 0x03) << 8) | frame_data[6]) * 45) >> 7;
|
||||
|
||||
int32_t raw = ((frame_data[7] & 0x7F) << 3) | (frame_data[8] >> 5);
|
||||
if (raw) { // check speed available
|
||||
velo.speed = raw - 1;
|
||||
velo.type = (frame_data[7] & 0x80) ? SPD_TAS : SPD_IAS; // set AirSpeed type
|
||||
|
||||
// supersonic indicator so multiply by 4
|
||||
if (velo_type == 4) velo.speed *= 4;
|
||||
}
|
||||
}
|
||||
|
||||
return velo;
|
||||
|
||||
@@ -50,8 +50,15 @@ enum data_selector {
|
||||
BDS_HEADING = 0x60
|
||||
};
|
||||
|
||||
enum speed_type {
|
||||
SPD_GND = 0, // Ground Speed
|
||||
SPD_IAS = 1, // Indicated AirSpeed
|
||||
SPD_TAS = 2 // True AirSpeed
|
||||
};
|
||||
|
||||
struct adsb_pos {
|
||||
bool valid;
|
||||
bool pos_valid;
|
||||
bool alt_valid;
|
||||
float latitude;
|
||||
float longitude;
|
||||
int32_t altitude;
|
||||
@@ -59,6 +66,7 @@ struct adsb_pos {
|
||||
|
||||
struct adsb_vel {
|
||||
bool valid;
|
||||
speed_type type; // ground speed, IAS or TAS
|
||||
int32_t speed; // knot
|
||||
uint16_t heading; // degree
|
||||
int32_t v_rate; // ft/min
|
||||
|
||||
@@ -80,20 +80,24 @@ class ADSBFrame {
|
||||
void make_CRC() {
|
||||
uint32_t computed_CRC = compute_CRC();
|
||||
|
||||
uint8_t crc_pos = (raw_data[0] & 0x80) ? 11 : 4;
|
||||
|
||||
// Insert CRC in frame
|
||||
raw_data[11] = (computed_CRC >> 16) & 0xFF;
|
||||
raw_data[12] = (computed_CRC >> 8) & 0xFF;
|
||||
raw_data[13] = computed_CRC & 0xFF;
|
||||
raw_data[crc_pos] = (computed_CRC >> 16) & 0xFF;
|
||||
raw_data[crc_pos + 1] = (computed_CRC >> 8) & 0xFF;
|
||||
raw_data[crc_pos + 2] = computed_CRC & 0xFF;
|
||||
}
|
||||
|
||||
bool check_CRC() {
|
||||
uint32_t check_CRC() {
|
||||
uint32_t computed_CRC = compute_CRC();
|
||||
|
||||
if ((raw_data[11] != ((computed_CRC >> 16) & 0xFF)) ||
|
||||
(raw_data[12] != ((computed_CRC >> 8) & 0xFF)) ||
|
||||
(raw_data[13] != (computed_CRC & 0xFF))) return false;
|
||||
uint8_t crc_pos = (raw_data[0] & 0x80) ? 11 : 4;
|
||||
|
||||
return true;
|
||||
uint32_t received_CRC = (raw_data[crc_pos] << 16) |
|
||||
(raw_data[crc_pos + 1] << 8) |
|
||||
raw_data[crc_pos + 2];
|
||||
|
||||
return (received_CRC ^ computed_CRC) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
bool empty() {
|
||||
@@ -111,12 +115,13 @@ class ADSBFrame {
|
||||
uint8_t adsb_crc[14] = {0}; // Temp buffer
|
||||
uint8_t b, c, s, bitn;
|
||||
const uint32_t crc_poly = 0x1205FFF;
|
||||
uint8_t data_len = (raw_data[0] & 0x80) ? 11 : 4;
|
||||
|
||||
// Copy frame data
|
||||
memcpy(adsb_crc, raw_data, 11);
|
||||
memcpy(adsb_crc, raw_data, data_len);
|
||||
|
||||
// Compute CRC
|
||||
for (c = 0; c < 11; c++) {
|
||||
for (c = 0; c < data_len; c++) {
|
||||
for (b = 0; b < 8; b++) {
|
||||
if ((adsb_crc[c] << b) & 0x80) {
|
||||
for (s = 0; s < 25; s++) {
|
||||
@@ -127,7 +132,7 @@ class ADSBFrame {
|
||||
}
|
||||
}
|
||||
|
||||
return (adsb_crc[11] << 16) + (adsb_crc[12] << 8) + adsb_crc[13];
|
||||
return (adsb_crc[data_len] << 16) + (adsb_crc[data_len + 1] << 8) + adsb_crc[data_len + 2];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1325,6 +1325,172 @@ constexpr fir_taps_real<16> taps_40k_wfm_decim_1 = {
|
||||
}},
|
||||
};
|
||||
|
||||
// WFMAM decimation filters ////////////////////////////////////////////////
|
||||
// Used for NOAA 137 Mhz APT sat demod.
|
||||
// IFIR prototype filter: fs=768000, pass=40000, stop=95000, decim=8, fout=96000
|
||||
constexpr fir_taps_real<32> taps_80k_wfmam_decim_1 = {
|
||||
.low_frequency_normalized = -40000.0f / 768000.0f,
|
||||
.high_frequency_normalized = 40000.0f / 768000.0f,
|
||||
.transition_normalized = 53000.0f / 768000.0f,
|
||||
.taps = {{
|
||||
5,
|
||||
-37,
|
||||
-120,
|
||||
-248,
|
||||
-397,
|
||||
-519,
|
||||
-535,
|
||||
-354,
|
||||
106,
|
||||
896,
|
||||
2006,
|
||||
3355,
|
||||
4797,
|
||||
6136,
|
||||
7171,
|
||||
7736,
|
||||
7736,
|
||||
7171,
|
||||
6136,
|
||||
4797,
|
||||
3355,
|
||||
2006,
|
||||
896,
|
||||
106,
|
||||
-354,
|
||||
-535,
|
||||
-519,
|
||||
-397,
|
||||
-248,
|
||||
-120,
|
||||
-37,
|
||||
5,
|
||||
|
||||
}},
|
||||
};
|
||||
|
||||
// WFMAM decimation filters ////////////////////////////////////////////////
|
||||
// Used for NOAA 137 Mhz APT sat demod.
|
||||
// IFIR prototype filter: fs=768000, pass=19000, stop=68000, decim=8, fout=96000
|
||||
constexpr fir_taps_real<32> taps_38k_wfmam_decim_1 = {
|
||||
.low_frequency_normalized = -19000.0f / 768000.0f,
|
||||
.high_frequency_normalized = 19000.0f / 768000.0f,
|
||||
.transition_normalized = 49000.0f / 768000.0f,
|
||||
.taps = {{
|
||||
49,
|
||||
91,
|
||||
175,
|
||||
303,
|
||||
483,
|
||||
724,
|
||||
1028,
|
||||
1391,
|
||||
1805,
|
||||
2253,
|
||||
2712,
|
||||
3158,
|
||||
3560,
|
||||
3891,
|
||||
4127,
|
||||
4250,
|
||||
4250,
|
||||
4127,
|
||||
3891,
|
||||
3560,
|
||||
3158,
|
||||
2712,
|
||||
2253,
|
||||
1805,
|
||||
1391,
|
||||
1028,
|
||||
724,
|
||||
483,
|
||||
303,
|
||||
175,
|
||||
91,
|
||||
49,
|
||||
}},
|
||||
};
|
||||
|
||||
/* 1st Wideband FM demod baseband filter of audio AM tones ,
|
||||
to pass all DSB band of AM fsubcarrier 2.4Khz mod. with APT */
|
||||
/* 24kHz int16_t input
|
||||
* -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop
|
||||
* -> 12kHz int16_t output, gain of 1.0 (I think).
|
||||
* sum(abs(taps)): 125152 , before <125270>, very similar.
|
||||
*/
|
||||
constexpr fir_taps_real<64> taps_64_lp_1875_2166{
|
||||
.low_frequency_normalized = -0.1875f,
|
||||
.high_frequency_normalized = 0.1875f,
|
||||
.transition_normalized = 0.03f,
|
||||
.taps = {{
|
||||
38,
|
||||
-21,
|
||||
-51,
|
||||
-9,
|
||||
77,
|
||||
82,
|
||||
-50,
|
||||
-168,
|
||||
-67,
|
||||
190,
|
||||
253,
|
||||
-61,
|
||||
-403,
|
||||
-243,
|
||||
356,
|
||||
616,
|
||||
15,
|
||||
-814,
|
||||
-671,
|
||||
550,
|
||||
1335,
|
||||
334,
|
||||
-1527,
|
||||
-1689,
|
||||
725,
|
||||
2978,
|
||||
1455,
|
||||
-3277,
|
||||
-5361,
|
||||
830,
|
||||
13781,
|
||||
24549,
|
||||
24549,
|
||||
13781,
|
||||
830,
|
||||
-5361,
|
||||
-3277,
|
||||
1455,
|
||||
2978,
|
||||
725,
|
||||
-1689,
|
||||
-1527,
|
||||
334,
|
||||
1335,
|
||||
550,
|
||||
-671,
|
||||
-814,
|
||||
15,
|
||||
616,
|
||||
356,
|
||||
-243,
|
||||
-403,
|
||||
-61,
|
||||
253,
|
||||
190,
|
||||
-67,
|
||||
-168,
|
||||
-50,
|
||||
82,
|
||||
77,
|
||||
-9,
|
||||
-51,
|
||||
-21,
|
||||
38,
|
||||
}},
|
||||
};
|
||||
|
||||
// TPMS decimation filters ////////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400
|
||||
|
||||
@@ -56,8 +56,8 @@ constexpr iir_biquad_config_t audio_12k_hpf_300hz_config{
|
||||
{1.00000000f, -1.77863178f, 0.80080265f}};
|
||||
|
||||
// scipy.signal.butter(2, 1500 / 6000.0, 'low', analog=False)
|
||||
constexpr iir_biquad_config_t audio_12k_lpf_1500hz_config{
|
||||
// Added to lpf the audio in wefax mode , before sending to SD card or spk.
|
||||
constexpr iir_biquad_config_t apt_audio_12k_lpf_1500hz_config{
|
||||
// Added to lpf the apt audio in wefax mode , before sending to SD card or spk.
|
||||
{0.09763107f, 0.19526215f, 0.09763107f},
|
||||
{1.00000000f, -0.94280904f, 0.33333333f}};
|
||||
|
||||
@@ -108,4 +108,15 @@ constexpr iir_biquad_config_t audio_48k_deemph_2122_6_config{
|
||||
{0.12264116f, 0.12264116f, 0.00000000f},
|
||||
{1.00000000f, -0.75471767f, 0.00000000f}};
|
||||
|
||||
// scipy.signal.iirnotch(f0, Q, fs) = signal.iirnotch(2400, 15, 12000)
|
||||
constexpr iir_biquad_config_t apt_audio_12k_notch_2k4_config{
|
||||
{0.95977357f, -0.59317269f, 0.95977357f},
|
||||
{1.00000000f, -0.59317269f, 0.91954714f}};
|
||||
|
||||
// scipy.signal.butter(2, 2000 / 6000.0, 'low', analog=False)
|
||||
constexpr iir_biquad_config_t apt_audio_12k_lpf_2000hz_config{
|
||||
// Added to lpf the apt audio in NOAA mode , before sending to SD card or spk.
|
||||
{0.15505103f, 0.31010205f, 0.15505103f},
|
||||
{1.00000000f, -0.6202041f, 0.24040821f}};
|
||||
|
||||
#endif /*__DSP_IIR_CONFIG_H__*/
|
||||
|
||||
@@ -33,6 +33,8 @@ using namespace portapack;
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <complex>
|
||||
|
||||
#include <cstring>
|
||||
@@ -143,7 +145,13 @@ void lcd_init() {
|
||||
// REV = 1 (normally white)
|
||||
// NL = 0b100111 (default)
|
||||
// PCDIV = 0b000000 (default?)
|
||||
io.lcd_data_write_command_and_data(0xB6, {0x0A, 0xA2, 0x27, 0x00});
|
||||
|
||||
/*as per the datasheet chapter 8.3.7, addr B6h,
|
||||
data "REV" bit, liquid crystal type:*/
|
||||
if (portapack::persistent_memory::config_lcd_normally_black())
|
||||
io.lcd_data_write_command_and_data(0xB6, {0x0A, 0x22, 0x27, 0x00}); // IPS : normally black : 0
|
||||
else
|
||||
io.lcd_data_write_command_and_data(0xB6, {0x0A, 0xA2, 0x27, 0x00}); // TFT : normally white : 1
|
||||
|
||||
// Power Control 1
|
||||
// VRH[5:0]
|
||||
@@ -309,14 +317,6 @@ void ILI9341::wake() {
|
||||
lcd_wake();
|
||||
}
|
||||
|
||||
void ILI9341::set_inverted(bool invert) {
|
||||
if (invert) {
|
||||
io.lcd_data_write_command_and_data(0x21, {});
|
||||
} else {
|
||||
io.lcd_data_write_command_and_data(0x20, {});
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9341::fill_rectangle(ui::Rect r, const ui::Color c) {
|
||||
const auto r_clipped = r.intersect(screen_rect());
|
||||
if (!r_clipped.is_empty()) {
|
||||
|
||||
@@ -50,8 +50,6 @@ class ILI9341 {
|
||||
void sleep();
|
||||
void wake();
|
||||
|
||||
void set_inverted(bool invert);
|
||||
|
||||
void fill_rectangle(ui::Rect r, const ui::Color c);
|
||||
void fill_rectangle_unrolled8(ui::Rect r, const ui::Color c);
|
||||
void draw_line(const ui::Point start, const ui::Point end, const ui::Color color);
|
||||
|
||||
@@ -130,7 +130,10 @@ class Message {
|
||||
WeFaxRxConfigure = 73,
|
||||
WeFaxRxStatusData = 74,
|
||||
WeFaxRxImageData = 75,
|
||||
|
||||
WFMAMConfigure = 76,
|
||||
NoaaAptRxConfigure = 77,
|
||||
NoaaAptRxStatusData = 78,
|
||||
NoaaAptRxImageData = 79,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -584,6 +587,32 @@ class WFMConfigureMessage : public Message {
|
||||
const iir_biquad_config_t audio_deemph_config;
|
||||
};
|
||||
|
||||
class WFMAMConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr WFMAMConfigureMessage(
|
||||
const fir_taps_real<24> decim_0_filter,
|
||||
const fir_taps_real<32> decim_1_filter,
|
||||
const fir_taps_real<64> audio_filter,
|
||||
const size_t deviation,
|
||||
const iir_biquad_config_t audio_hpf_config,
|
||||
const iir_biquad_config_t audio_deemph_config)
|
||||
: Message{ID::WFMAMConfigure},
|
||||
decim_0_filter(decim_0_filter),
|
||||
decim_1_filter(decim_1_filter),
|
||||
audio_filter(audio_filter),
|
||||
deviation{deviation},
|
||||
audio_hpf_config(audio_hpf_config),
|
||||
audio_deemph_config(audio_deemph_config) {
|
||||
}
|
||||
|
||||
const fir_taps_real<24> decim_0_filter;
|
||||
const fir_taps_real<32> decim_1_filter;
|
||||
const fir_taps_real<64> audio_filter;
|
||||
const size_t deviation;
|
||||
const iir_biquad_config_t audio_hpf_config;
|
||||
const iir_biquad_config_t audio_deemph_config;
|
||||
};
|
||||
|
||||
class AMConfigureMessage : public Message {
|
||||
public:
|
||||
enum class Modulation : int32_t {
|
||||
@@ -1478,4 +1507,27 @@ class WeFaxRxImageDataMessage : public Message {
|
||||
uint32_t cnt = 0;
|
||||
};
|
||||
|
||||
class NoaaAptRxConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr NoaaAptRxConfigureMessage()
|
||||
: Message{ID::NoaaAptRxConfigure} {}
|
||||
};
|
||||
|
||||
class NoaaAptRxStatusDataMessage : public Message {
|
||||
public:
|
||||
constexpr NoaaAptRxStatusDataMessage(uint8_t state)
|
||||
: Message{ID::NoaaAptRxStatusData},
|
||||
state{state} {
|
||||
}
|
||||
uint8_t state = 0;
|
||||
};
|
||||
|
||||
class NoaaAptRxImageDataMessage : public Message {
|
||||
public:
|
||||
constexpr NoaaAptRxImageDataMessage()
|
||||
: Message{ID::NoaaAptRxImageData} {}
|
||||
uint8_t image[400]{0};
|
||||
uint32_t cnt = 0;
|
||||
};
|
||||
|
||||
#endif /*__MESSAGE_H__*/
|
||||
|
||||
@@ -78,11 +78,11 @@ void IO::reference_oscillator(const bool enable) {
|
||||
}
|
||||
|
||||
bool IO::get_dark_cover() {
|
||||
return portapack::persistent_memory::apply_fake_brightness() & (!portapack::persistent_memory::config_lcd_inverted_mode());
|
||||
return portapack::persistent_memory::apply_fake_brightness();
|
||||
}
|
||||
|
||||
bool IO::get_is_inverted() {
|
||||
return portapack::persistent_memory::config_lcd_inverted_mode();
|
||||
bool IO::get_is_normally_black() {
|
||||
return portapack::persistent_memory::config_lcd_normally_black();
|
||||
}
|
||||
|
||||
uint8_t IO::get_brightness() {
|
||||
@@ -90,7 +90,7 @@ uint8_t IO::get_brightness() {
|
||||
}
|
||||
|
||||
void IO::update_cached_values() {
|
||||
inverted_enabled = get_is_inverted();
|
||||
lcd_normally_black = get_is_normally_black();
|
||||
dark_cover_enabled = get_dark_cover();
|
||||
brightness = get_brightness();
|
||||
}
|
||||
|
||||
@@ -231,10 +231,10 @@ class IO {
|
||||
|
||||
return switches_raw;
|
||||
}
|
||||
bool inverted_enabled = false;
|
||||
bool lcd_normally_black = false;
|
||||
bool dark_cover_enabled = false;
|
||||
uint8_t brightness = 0;
|
||||
bool get_is_inverted();
|
||||
bool get_is_normally_black();
|
||||
bool get_dark_cover();
|
||||
uint8_t get_brightness();
|
||||
void update_cached_values();
|
||||
@@ -419,7 +419,7 @@ class IO {
|
||||
const auto value_low = data_read();
|
||||
uint32_t original_value = (value_high << 8) | value_low;
|
||||
|
||||
if (inverted_enabled) return original_value;
|
||||
if (lcd_normally_black) return original_value;
|
||||
|
||||
if (dark_cover_enabled) {
|
||||
// this is read data, so if the fake brightness is enabled AKA get_dark_cover() == true,
|
||||
|
||||
@@ -211,7 +211,7 @@ struct data_t {
|
||||
bool updown_converter;
|
||||
bool updown_frequency_rx_correction;
|
||||
bool updown_frequency_tx_correction;
|
||||
bool lcd_inverted_mode : 1;
|
||||
bool lcd_normally_black : 1;
|
||||
bool encoder_dial_direction : 1; // false = normal, true = reverse
|
||||
bool UNUSED_6 : 1;
|
||||
bool UNUSED_7 : 1;
|
||||
@@ -289,7 +289,7 @@ struct data_t {
|
||||
updown_converter(false),
|
||||
updown_frequency_rx_correction(false),
|
||||
updown_frequency_tx_correction(false),
|
||||
lcd_inverted_mode(false),
|
||||
lcd_normally_black(false),
|
||||
encoder_dial_direction(false),
|
||||
UNUSED_6(false),
|
||||
UNUSED_7(false),
|
||||
@@ -1090,12 +1090,12 @@ void set_config_freq_rx_correction(uint32_t v) {
|
||||
data->frequency_rx_correction = v;
|
||||
}
|
||||
|
||||
// LCD invert
|
||||
bool config_lcd_inverted_mode() {
|
||||
return data->lcd_inverted_mode;
|
||||
// IPS vs TFT
|
||||
bool config_lcd_normally_black() {
|
||||
return data->lcd_normally_black;
|
||||
}
|
||||
void set_lcd_inverted_mode(bool v) {
|
||||
data->lcd_inverted_mode = v;
|
||||
void set_lcd_normally_black(bool v) {
|
||||
data->lcd_normally_black = v;
|
||||
}
|
||||
|
||||
// Rotary encoder dial settings
|
||||
@@ -1164,7 +1164,6 @@ void set_fake_brightness_level(uint8_t v) {
|
||||
// Cycle through 4 brightness options: disabled -> enabled/50% -> enabled/25% -> enabled/12.5% -> disabled
|
||||
void toggle_fake_brightness_level() {
|
||||
bool fbe = apply_fake_brightness();
|
||||
if (config_lcd_inverted_mode()) return; // for now only inverted mode OR fake brightness
|
||||
if ((!fbe) || (data->fake_brightness_level >= BRIGHTNESS_12p5)) {
|
||||
set_apply_fake_brightness(!fbe);
|
||||
data->fake_brightness_level = BRIGHTNESS_50;
|
||||
|
||||
@@ -249,8 +249,8 @@ void set_config_audio_mute(bool v);
|
||||
void set_config_speaker_disable(bool v);
|
||||
void set_config_backlight_timer(const backlight_config_t& new_value);
|
||||
void set_disable_touchscreen(bool v);
|
||||
bool config_lcd_inverted_mode();
|
||||
void set_lcd_inverted_mode(bool v);
|
||||
bool config_lcd_normally_black();
|
||||
void set_lcd_normally_black(bool v);
|
||||
|
||||
uint8_t encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
|
||||
@@ -119,6 +119,7 @@ constexpr image_tag_t image_tag_weather{'P', 'W', 'T', 'H'};
|
||||
constexpr image_tag_t image_tag_subghzd{'P', 'S', 'G', 'D'};
|
||||
constexpr image_tag_t image_tag_protoview{'P', 'P', 'V', 'W'};
|
||||
constexpr image_tag_t image_tag_wefaxrx{'P', 'W', 'F', 'X'};
|
||||
constexpr image_tag_t image_tag_noaaapt_rx{'P', 'N', 'O', 'A'};
|
||||
|
||||
constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ enum app_location_t : uint32_t {
|
||||
DEBUG,
|
||||
HOME,
|
||||
SETTINGS,
|
||||
GAMES
|
||||
GAMES,
|
||||
TRX
|
||||
};
|
||||
|
||||
struct standalone_application_information_t {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 365 B |
Binary file not shown.
|
After Width: | Height: | Size: 185 B |
@@ -98,12 +98,12 @@ f.write("/*\n"
|
||||
"#include \"ui.hpp\"\n\n"
|
||||
"namespace ui {\n\n")
|
||||
|
||||
for file in listdir(graphics_path):
|
||||
for file in sorted(listdir(graphics_path)):
|
||||
if file.endswith(".png") and path.isfile(graphics_path + file):
|
||||
convert_png(graphics_path + file)
|
||||
count += 1
|
||||
|
||||
f.write("} /* namespace ui */\n\n")
|
||||
f.write("#endif/*__BITMAP_HPP__*/\n")
|
||||
f.write("#endif /*__BITMAP_HPP__*/\n")
|
||||
|
||||
print(("Converted " + str(count) + " files"))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 0% black
|
||||
0,0,0,0
|
||||
|
||||
# 25% blue
|
||||
64,0,0,255
|
||||
|
||||
# 50% magenta
|
||||
128,255,0,255
|
||||
|
||||
# 75% green
|
||||
192,0,255,0
|
||||
|
||||
# 100% white
|
||||
255,255,255,255
|
||||
@@ -0,0 +1,12 @@
|
||||
#lines are index,r,g,b
|
||||
#if index 0 is not set, it's starting at 0,0,0,0
|
||||
#all values are ranging from 0 to 255
|
||||
|
||||
#pure blue
|
||||
86,0,0,255
|
||||
|
||||
#bright green
|
||||
171,0,255,0
|
||||
|
||||
#pure red
|
||||
255,255,0,0
|
||||
@@ -0,0 +1,11 @@
|
||||
# 0% black
|
||||
0,0,0,0
|
||||
|
||||
# 33% red
|
||||
84,255,0,0
|
||||
|
||||
# 66% yellow
|
||||
168,255,255,0
|
||||
|
||||
# 100% white
|
||||
255,255,255,255
|
||||
@@ -0,0 +1,8 @@
|
||||
# 0% black
|
||||
0,0,0,0
|
||||
|
||||
# 50% green
|
||||
128,0,255,0
|
||||
|
||||
# 100% white
|
||||
255,255,255,255
|
||||
@@ -0,0 +1,14 @@
|
||||
# 0% black
|
||||
0,0,0,0
|
||||
|
||||
# 25% blue
|
||||
64,0,0,255
|
||||
|
||||
# 50% red
|
||||
128,255,0,0
|
||||
|
||||
# 75% yellow
|
||||
192,255,255,0
|
||||
|
||||
# 100% white
|
||||
255,255,255,255
|
||||
Reference in New Issue
Block a user