mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
commit
c626d83c3b
@ -214,7 +214,7 @@ set(CPPSRC
|
||||
ui/ui_tabview.cpp
|
||||
ui/ui_textentry.cpp
|
||||
ui/ui_transmitter.cpp
|
||||
apps/ui_about.cpp
|
||||
apps/ui_about_simple.cpp
|
||||
apps/ui_adsb_rx.cpp
|
||||
apps/ui_adsb_tx.cpp
|
||||
apps/ui_afsk_rx.cpp
|
||||
|
@ -240,7 +240,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||
|
||||
void AISRecentEntryDetailView::update_position() {
|
||||
if (send_updates)
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()));
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading);
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
|
@ -83,6 +83,32 @@ NBFMOptionsView::NBFMOptionsView(
|
||||
};
|
||||
}
|
||||
|
||||
/* SPECOptionsView *******************************************************/
|
||||
|
||||
SPECOptionsView::SPECOptionsView(
|
||||
AnalogAudioView* view, const Rect parent_rect, const Style* const style
|
||||
) : View { parent_rect }
|
||||
{
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
&text_speed,
|
||||
&field_speed
|
||||
});
|
||||
|
||||
options_config.set_selected_index(view->get_spec_bw_index());
|
||||
options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
|
||||
view->set_spec_bw(n, bw);
|
||||
};
|
||||
|
||||
field_speed.set_value(view->get_spec_trigger());
|
||||
field_speed.on_change = [this, view](int32_t v) {
|
||||
view->set_spec_trigger(v);
|
||||
};
|
||||
}
|
||||
|
||||
/* AnalogAudioView *******************************************************/
|
||||
|
||||
AnalogAudioView::AnalogAudioView(
|
||||
@ -157,6 +183,29 @@ AnalogAudioView::AnalogAudioView(
|
||||
on_modulation_changed(static_cast<ReceiverModel::Mode>(modulation));
|
||||
}
|
||||
|
||||
size_t AnalogAudioView::get_spec_bw_index() {
|
||||
return spec_bw_index;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
|
||||
spec_bw_index = index;
|
||||
spec_bw = bw;
|
||||
|
||||
baseband::set_spectrum(bw, spec_trigger);
|
||||
receiver_model.set_sampling_rate(bw);
|
||||
receiver_model.set_baseband_bandwidth(bw/2);
|
||||
}
|
||||
|
||||
uint16_t AnalogAudioView::get_spec_trigger() {
|
||||
return spec_trigger;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
|
||||
spec_trigger = trigger;
|
||||
|
||||
baseband::set_spectrum(spec_bw, spec_trigger);
|
||||
}
|
||||
|
||||
AnalogAudioView::~AnalogAudioView() {
|
||||
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
|
||||
// both?
|
||||
@ -272,6 +321,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, &style_options_group);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
@ -315,15 +365,17 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
}
|
||||
|
||||
baseband::run_image(image_tag);
|
||||
|
||||
|
||||
if (modulation == ReceiverModel::Mode::SpectrumAnalysis) {
|
||||
baseband::set_spectrum(20000000, 127);
|
||||
baseband::set_spectrum(spec_bw, spec_trigger);
|
||||
}
|
||||
|
||||
const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.set_modulation(modulation);
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? 20000000 : 3072000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? 12000000 : 1750000);
|
||||
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw/2 : 1750000);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
// TODO: This doesn't belong here! There's a better way.
|
||||
|
@ -94,6 +94,43 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class AnalogAudioView;
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
public:
|
||||
SPECOptionsView(AnalogAudioView* view, const Rect parent_rect, const Style* const style);
|
||||
|
||||
private:
|
||||
Text label_config {
|
||||
{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
4,
|
||||
{
|
||||
{ "20m ", 20000000 },
|
||||
{ "10m ", 10000000 },
|
||||
{ " 5m ", 5000000 },
|
||||
{ " 2m ", 2000000 },
|
||||
{ " 1m ", 1000000 },
|
||||
{ "500k", 500000 },
|
||||
}
|
||||
};
|
||||
|
||||
Text text_speed {
|
||||
{ 9 * 8, 0 * 16, 8 * 8, 1 * 16 },
|
||||
"SP /63"
|
||||
};
|
||||
NumberField field_speed {
|
||||
{ 12 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 63 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
};
|
||||
|
||||
class AnalogAudioView : public View {
|
||||
public:
|
||||
AnalogAudioView(NavigationView& nav);
|
||||
@ -106,13 +143,23 @@ public:
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Analog audio"; };
|
||||
|
||||
|
||||
size_t get_spec_bw_index();
|
||||
void set_spec_bw(size_t index, uint32_t bw);
|
||||
|
||||
uint16_t get_spec_trigger();
|
||||
void set_spec_trigger(uint16_t trigger);
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 };
|
||||
const Rect nbfm_view_rect { 0 * 8, 1 * 16, 18 * 8, 1 * 16 };
|
||||
|
||||
size_t spec_bw_index = 0;
|
||||
uint32_t spec_bw = 20000000;
|
||||
uint16_t spec_trigger = 63;
|
||||
|
||||
NavigationView& nav_;
|
||||
//bool exit_on_squelch { false };
|
||||
|
||||
|
75
firmware/application/apps/ui_about_simple.cpp
Normal file
75
firmware/application/apps/ui_about_simple.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "ui_about_simple.hpp"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
AboutView::AboutView(NavigationView &nav)
|
||||
{
|
||||
add_children({&console, &button_ok});
|
||||
|
||||
button_ok.on_select = [&nav](Button &) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
console.writeln("\x1B\x07List of contributors:\x1B\x10");
|
||||
console.writeln("");
|
||||
}
|
||||
|
||||
void AboutView::update()
|
||||
{
|
||||
if (++timer > 200)
|
||||
{
|
||||
timer = 0;
|
||||
|
||||
switch (++frame)
|
||||
{
|
||||
case 1:
|
||||
// TODO: Generate this automatically from github
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
|
||||
console.writeln("\x1B\x06Mayhem:\x1B\x10");
|
||||
console.writeln("eried,euquiq,gregoryfenton");
|
||||
console.writeln("johnelder,jwetzell,nnemanjan00");
|
||||
console.writeln("N0vaPixel,klockee,jamesshao8");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
|
||||
console.writeln("\x1B\x06Havoc:\x1B\x10");
|
||||
console.writeln("furrtek,mrmookie,notpike");
|
||||
console.writeln("mjwaxios,ImDroided,Giorgiofox");
|
||||
console.writeln("F4GEV,z4ziggy,xmycroftx");
|
||||
console.writeln("troussos,silascutler");
|
||||
console.writeln("nickbouwhuis,msoose,leres");
|
||||
console.writeln("joakar,dhoetger,clem-42");
|
||||
console.writeln("brianlechthaler,ZeroChaos-...");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
|
||||
console.writeln("\x1B\x06PortaPack:\x1B\x10");
|
||||
console.writeln("jboone,argilo");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// https://github.com/mossmann/hackrf/graphs/contributors
|
||||
console.writeln("\x1B\x06HackRF:\x1B\x10");
|
||||
console.writeln("mossmann,dominicgs,bvernoux");
|
||||
console.writeln("bgamari,schneider42,miek");
|
||||
console.writeln("willcode,hessu,Sec42");
|
||||
console.writeln("yhetti,ckuethe,smunaut");
|
||||
console.writeln("wishi,mrbubble62,scateu...");
|
||||
console.writeln("");
|
||||
frame = 0; // Loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus()
|
||||
{
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
40
firmware/application/apps/ui_about_simple.hpp
Normal file
40
firmware/application/apps/ui_about_simple.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef __UI_ABOUT_SIMPLE_H__
|
||||
#define __UI_ABOUT_SIMPLE_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class AboutView : public View
|
||||
{
|
||||
public:
|
||||
AboutView(NavigationView &nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "About"; };
|
||||
int32_t timer{180};
|
||||
short frame{0};
|
||||
|
||||
private:
|
||||
void update();
|
||||
|
||||
Console console{
|
||||
{0, 10, 240, 240}};
|
||||
|
||||
Button button_ok{
|
||||
{240/3, 270, 240/3, 24},
|
||||
"OK",
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_update{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message *const) {
|
||||
this->update();
|
||||
}};
|
||||
};
|
||||
} // namespace ui
|
||||
|
||||
#endif /*__UI_ABOUT_SIMPLE_H__*/
|
@ -92,12 +92,16 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
|
||||
text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago");
|
||||
|
||||
text_infos.set(entry_copy.info_string);
|
||||
|
||||
if(entry_copy.velo.heading < 360 && entry_copy.velo.speed >=0){ //I don't like this but...
|
||||
text_info2.set("Hdg:" + to_string_dec_uint(entry_copy.velo.heading) + " Spd:" + to_string_dec_int(entry_copy.velo.speed));
|
||||
}else{
|
||||
text_info2.set("");
|
||||
}
|
||||
text_frame_pos_even.set(to_string_hex_array(entry_copy.frame_pos_even.get_raw_data(), 14));
|
||||
text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
|
||||
|
||||
if (send_updates)
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude);
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading);
|
||||
}
|
||||
|
||||
ADSBRxDetailsView::~ADSBRxDetailsView() {
|
||||
@ -123,6 +127,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
&text_airline,
|
||||
&text_country,
|
||||
&text_infos,
|
||||
&text_info2,
|
||||
&text_frame_pos_even,
|
||||
&text_frame_pos_odd,
|
||||
&button_see_map
|
||||
@ -172,7 +177,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
GeoPos::alt_unit::FEET,
|
||||
entry_copy.pos.latitude,
|
||||
entry_copy.pos.longitude,
|
||||
0,
|
||||
entry_copy.velo.heading,
|
||||
[this]() {
|
||||
send_updates = false;
|
||||
});
|
||||
@ -199,7 +204,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
|
||||
auto frame = message->frame;
|
||||
uint32_t ICAO_address = frame.get_ICAO_address();
|
||||
|
||||
|
||||
if (frame.check_CRC() && frame.get_ICAO_address()) {
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
auto& entry = ::on_packet(recent, ICAO_address);
|
||||
@ -214,6 +219,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
|
||||
if (frame.get_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();
|
||||
|
||||
if ((msg_type >= 1) && (msg_type <= 4)) {
|
||||
@ -224,10 +230,10 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
entry.set_frame_pos(frame, raw_data[6] & 4);
|
||||
|
||||
if (entry.pos.valid) {
|
||||
str_info = "Alt:" + to_string_dec_uint(entry.pos.altitude) +
|
||||
" Lat" + to_string_dec_int(entry.pos.latitude) +
|
||||
str_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
|
||||
" Lat:" + to_string_dec_int(entry.pos.latitude) +
|
||||
"." + to_string_dec_int((int)abs(entry.pos.latitude * 1000) % 100, 2, '0') +
|
||||
" Lon" + to_string_dec_int(entry.pos.longitude) +
|
||||
" Lon:" + to_string_dec_int(entry.pos.longitude) +
|
||||
"." + to_string_dec_int((int)abs(entry.pos.longitude * 1000) % 100, 2, '0');
|
||||
|
||||
entry.set_info_string(str_info);
|
||||
@ -236,6 +242,13 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
if (send_updates)
|
||||
details_view->update(entry);
|
||||
}
|
||||
} else if(msg_type == 19 && msg_sub >= 1 && msg_sub <= 4){
|
||||
entry.set_frame_velo(frame);
|
||||
logentry += "Type:" + to_string_dec_uint(msg_sub) +
|
||||
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
|
||||
" Spd: "+ to_string_dec_int(entry.velo.speed);
|
||||
if (send_updates)
|
||||
details_view->update(entry);
|
||||
}
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
|
@ -49,7 +49,7 @@ struct AircraftRecentEntry {
|
||||
uint16_t hits { 0 };
|
||||
uint32_t age { 0 };
|
||||
adsb_pos pos { false, 0, 0, 0 };
|
||||
|
||||
adsb_vel velo { false, 0, 999 };
|
||||
ADSBFrame frame_pos_even { };
|
||||
ADSBFrame frame_pos_odd { };
|
||||
|
||||
@ -86,6 +86,10 @@ struct AircraftRecentEntry {
|
||||
pos = decode_frame_pos(frame_pos_even, frame_pos_odd);
|
||||
}
|
||||
}
|
||||
|
||||
void set_frame_velo(ADSBFrame& frame){
|
||||
velo = decode_frame_velo(frame);
|
||||
}
|
||||
|
||||
void set_info_string(std::string& new_info_string) {
|
||||
info_string = new_info_string;
|
||||
@ -146,8 +150,8 @@ private:
|
||||
{ { 0 * 8, 2 * 16 }, "Last seen:", Color::light_grey() },
|
||||
{ { 0 * 8, 3 * 16 }, "Airline:", Color::light_grey() },
|
||||
{ { 0 * 8, 5 * 16 }, "Country:", Color::light_grey() },
|
||||
{ { 0 * 8, 12 * 16 }, "Even position frame:", Color::light_grey() },
|
||||
{ { 0 * 8, 14 * 16 }, "Odd position frame:", Color::light_grey() }
|
||||
{ { 0 * 8, 13 * 16 }, "Even position frame:", Color::light_grey() },
|
||||
{ { 0 * 8, 15 * 16 }, "Odd position frame:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_callsign {
|
||||
@ -174,17 +178,23 @@ private:
|
||||
{ 0 * 8, 6 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_info2 {
|
||||
{0*8, 7*16, 30*8, 16},
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_frame_pos_even {
|
||||
{ 0 * 8, 13 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 14 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
Text text_frame_pos_odd {
|
||||
{ 0 * 8, 15 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 16 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Button button_see_map {
|
||||
{ 8 * 8, 8 * 16, 14 * 8, 3 * 16 },
|
||||
{ 8 * 8, 9 * 16, 14 * 8, 3 * 16 },
|
||||
"See on map"
|
||||
};
|
||||
};
|
||||
|
@ -121,7 +121,7 @@ void TemperatureWidget::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
|
||||
return -45 + sensor_value * 5;
|
||||
return -35 + sensor_value * 4; //max2837 datasheet temp 25ºC has sensor value: 15
|
||||
}
|
||||
|
||||
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
|
||||
|
@ -101,10 +101,10 @@ private:
|
||||
|
||||
std::string temperature_str(const temperature_t temperature) const;
|
||||
|
||||
static constexpr temperature_t display_temp_min = 0;
|
||||
static constexpr temperature_t display_temp_min = -10; //Accomodate negative values, present in cold startup cases
|
||||
static constexpr temperature_t display_temp_scale = 3;
|
||||
static constexpr int bar_width = 1;
|
||||
static constexpr int temp_len = 3;
|
||||
static constexpr int temp_len = 4; //Now scale shows up to 4 chars ("-10C")
|
||||
};
|
||||
|
||||
class TemperatureView : public View {
|
||||
|
@ -61,22 +61,22 @@ void MicTXView::configure_baseband() {
|
||||
|
||||
void MicTXView::set_tx(bool enable) {
|
||||
if (enable) {
|
||||
if (rx_enabled) //If audio RX is enabled
|
||||
rxaudio(false); //Then turn off audio RX
|
||||
transmitting = true;
|
||||
configure_baseband();
|
||||
transmitter_model.enable();
|
||||
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
|
||||
//gpio_tx.write(1);
|
||||
//led_tx.on();
|
||||
} else {
|
||||
if (transmitting && rogerbeep_enabled) {
|
||||
baseband::request_beep();
|
||||
transmitting = false;
|
||||
} else {
|
||||
baseband::request_beep(); //Transmit the roger beep
|
||||
transmitting = false; //And flag the end of the transmission so ...
|
||||
} else { // (if roger beep was enabled, this will be executed after the beep ends transmitting.
|
||||
transmitting = false;
|
||||
configure_baseband();
|
||||
transmitter_model.disable();
|
||||
//gpio_tx.write(0);
|
||||
//led_tx.off();
|
||||
if (rx_enabled) //If audio RX is enabled and we've been transmitting
|
||||
rxaudio(true); //Turn back on audio RX
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,6 +120,39 @@ void MicTXView::do_timing() {
|
||||
|
||||
void MicTXView::on_tuning_frequency_changed(rf::Frequency f) {
|
||||
transmitter_model.set_tuning_frequency(f);
|
||||
//if ( rx_enabled )
|
||||
receiver_model.set_tuning_frequency(f); //Update freq also for RX
|
||||
}
|
||||
|
||||
void MicTXView::rxaudio(bool is_on) {
|
||||
if (is_on) {
|
||||
audio::input::stop();
|
||||
baseband::shutdown();
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
|
||||
receiver_model.enable();
|
||||
audio::output::start();
|
||||
} else { //These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
|
||||
audio::input::start();
|
||||
transmitter_model.enable();
|
||||
portapack::pin_i2s0_rx_sda.mode(3);
|
||||
transmitting = false;
|
||||
configure_baseband();
|
||||
transmitter_model.disable();
|
||||
}
|
||||
}
|
||||
|
||||
void MicTXView::on_headphone_volume_changed(int32_t v) {
|
||||
//if (rx_enabled) {
|
||||
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
|
||||
receiver_model.set_headphone_volume(new_volume);
|
||||
//}
|
||||
}
|
||||
|
||||
MicTXView::MicTXView(
|
||||
@ -142,9 +175,12 @@ MicTXView::MicTXView(
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
&check_rogerbeep,
|
||||
&check_rxactive,
|
||||
&field_volume,
|
||||
&field_squelch,
|
||||
&text_ptt
|
||||
});
|
||||
|
||||
|
||||
tone_keys_populate(options_tone_key);
|
||||
options_tone_key.on_change = [this](size_t i, int32_t) {
|
||||
tone_key_index = i;
|
||||
@ -168,6 +204,7 @@ MicTXView::MicTXView(
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_tuning_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
set_dirty();
|
||||
};
|
||||
};
|
||||
|
||||
@ -178,16 +215,15 @@ MicTXView::MicTXView(
|
||||
|
||||
check_va.on_select = [this](Checkbox&, bool v) {
|
||||
va_enabled = v;
|
||||
text_ptt.hidden(v);
|
||||
set_dirty();
|
||||
text_ptt.hidden(v); //hide / show PTT text
|
||||
check_rxactive.hidden(v); //hide / show the RX AUDIO
|
||||
set_dirty(); //Refresh display
|
||||
};
|
||||
check_va.set_value(false);
|
||||
|
||||
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
|
||||
rogerbeep_enabled = v;
|
||||
};
|
||||
check_rogerbeep.set_value(false);
|
||||
|
||||
|
||||
field_va_level.on_change = [this](int32_t v) {
|
||||
va_level = v;
|
||||
vumeter.set_mark(v);
|
||||
@ -203,7 +239,24 @@ MicTXView::MicTXView(
|
||||
decay_ms = v;
|
||||
};
|
||||
field_va_decay.set_value(1000);
|
||||
|
||||
|
||||
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
||||
//vumeter.set_value(0); //Start with a clean vumeter
|
||||
rx_enabled = v;
|
||||
check_va.hidden(v); //Hide or show voice activation
|
||||
rxaudio(v); //Activate-Deactivate audio rx accordingly
|
||||
set_dirty(); //Refresh interface
|
||||
};
|
||||
|
||||
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
|
||||
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
|
||||
|
||||
field_squelch.on_change = [this](int32_t v) {
|
||||
receiver_model.set_squelch_level(100 - v);
|
||||
};
|
||||
field_squelch.set_value(0);
|
||||
receiver_model.set_squelch_level(0);
|
||||
|
||||
transmitter_model.set_sampling_rate(sampling_rate);
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
|
||||
@ -216,6 +269,8 @@ MicTXView::MicTXView(
|
||||
MicTXView::~MicTXView() {
|
||||
audio::input::stop();
|
||||
transmitter_model.disable();
|
||||
if (rx_enabled) //Also turn off audio rx if enabled
|
||||
rxaudio(false);
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "transmitter_model.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "message.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@ -54,11 +55,11 @@ public:
|
||||
return false;
|
||||
};
|
||||
|
||||
std::string title() const override { return "Microphone TX"; };
|
||||
std::string title() const override { return "Mic TX RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t sampling_rate = 1536000U;
|
||||
static constexpr uint32_t lcd_frame_duration = (256 * 1000UL) / 60; // 1 frame @ 60fps in ms .8 fixed point
|
||||
static constexpr uint32_t lcd_frame_duration = (256 * 1000UL) / 60; // 1 frame @ 60fps in ms .8 fixed point /60
|
||||
|
||||
void update_vumeter();
|
||||
void do_timing();
|
||||
@ -66,10 +67,14 @@ private:
|
||||
void on_tuning_frequency_changed(rf::Frequency f);
|
||||
void on_tx_progress(const bool done);
|
||||
void configure_baseband();
|
||||
|
||||
void rxaudio(bool is_on);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
|
||||
bool transmitting { false };
|
||||
bool va_enabled { };
|
||||
bool rogerbeep_enabled { };
|
||||
bool va_enabled { false };
|
||||
bool rogerbeep_enabled { false };
|
||||
bool rx_enabled { false };
|
||||
uint32_t tone_key_index { };
|
||||
float mic_gain { 1.0 };
|
||||
uint32_t audio_level { 0 };
|
||||
@ -80,23 +85,25 @@ private:
|
||||
uint32_t decay_timer { 0 };
|
||||
|
||||
Labels labels {
|
||||
{ { 7 * 8, 1 * 8 }, "Mic. gain:", Color::light_grey() },
|
||||
{ { 7 * 8, 4 * 8 }, "Frequency:", Color::light_grey() },
|
||||
{ { 7 * 8, 6 * 8 }, "Bandwidth: kHz", Color::light_grey() },
|
||||
{ { 9 * 8, 13 * 8 }, "Level: /255", Color::light_grey() },
|
||||
{ { 9 * 8, 15 * 8 }, "Attack: ms", Color::light_grey() },
|
||||
{ { 9 * 8, 17 * 8 }, "Decay: ms", Color::light_grey() },
|
||||
{ { 7 * 8, 21 * 8 }, "Tone key:", Color::light_grey() }
|
||||
{ { 3 * 8, 1 * 8 }, "MIC. GAIN:", Color::light_grey() },
|
||||
{ { 3 * 8, 3 * 8 }, "FREQUENCY:", Color::light_grey() },
|
||||
{ { 3 * 8, 5 * 8 }, "BANDWIDTH: kHz", Color::light_grey() },
|
||||
{ { 7 * 8, 11 * 8 }, "LEVEL: /255", Color::light_grey() },
|
||||
{ { 6 * 8, 13 * 8 }, "ATTACK: ms", Color::light_grey() },
|
||||
{ { 7 * 8, 15 * 8 }, "DECAY: ms", Color::light_grey() },
|
||||
{ { 4 * 8, 18 * 8 }, "TONE KEY:", Color::light_grey() },
|
||||
{ { 9 * 8, 30 * 8 }, "VOL:", Color::light_grey() },
|
||||
{ { 5 * 8, 32 * 8 }, "SQUELCH:", Color::light_grey() }
|
||||
};
|
||||
|
||||
VuMeter vumeter {
|
||||
{ 1 * 8, 2 * 8, 5 * 8, 32 * 8 },
|
||||
20,
|
||||
false
|
||||
{ 0 * 8, 1 * 8, 2 * 8, 33 * 8 },
|
||||
12,
|
||||
true
|
||||
};
|
||||
|
||||
OptionsField options_gain {
|
||||
{ 17 * 8, 1 * 8 },
|
||||
{ 13 * 8, 1 * 8 },
|
||||
4,
|
||||
{
|
||||
{ "x0.5", 5 },
|
||||
@ -107,10 +114,10 @@ private:
|
||||
};
|
||||
|
||||
FrequencyField field_frequency {
|
||||
{ 17 * 8, 4 * 8 },
|
||||
{ 13 * 8, 3 * 8 },
|
||||
};
|
||||
NumberField field_bw {
|
||||
{ 17 * 8, 6 * 8 },
|
||||
{ 13 * 8, 5 * 8 },
|
||||
3,
|
||||
{ 0, 150 },
|
||||
1,
|
||||
@ -118,28 +125,28 @@ private:
|
||||
};
|
||||
|
||||
Checkbox check_va {
|
||||
{ 7 * 8, 10 * 8 },
|
||||
{ 3 * 8, (9 * 8) - 4 },
|
||||
7,
|
||||
"Voice activation",
|
||||
false
|
||||
};
|
||||
|
||||
NumberField field_va_level {
|
||||
{ 15 * 8, 13 * 8 },
|
||||
{ 13 * 8, 11 * 8 },
|
||||
3,
|
||||
{ 0, 255 },
|
||||
2,
|
||||
' '
|
||||
};
|
||||
NumberField field_va_attack {
|
||||
{ 16 * 8, 15 * 8 },
|
||||
{ 13 * 8, 13 * 8 },
|
||||
3,
|
||||
{ 0, 999 },
|
||||
20,
|
||||
' '
|
||||
};
|
||||
NumberField field_va_decay {
|
||||
{ 15 * 8, 17 * 8 },
|
||||
{ 13 * 8, 15 * 8 },
|
||||
4,
|
||||
{ 0, 9999 },
|
||||
100,
|
||||
@ -147,28 +154,52 @@ private:
|
||||
};
|
||||
|
||||
OptionsField options_tone_key {
|
||||
{ 7 * 8, 23 * 8 },
|
||||
{ 10 * 8, 20 * 8 },
|
||||
23,
|
||||
{ }
|
||||
};
|
||||
|
||||
Checkbox check_rogerbeep {
|
||||
{ 7 * 8, 26 * 8 },
|
||||
{ 3 * 8, 23 * 8 },
|
||||
10,
|
||||
"Roger beep",
|
||||
false
|
||||
};
|
||||
|
||||
Text text_ptt {
|
||||
{ 7 * 8, 17 * 16, 16 * 8, 16 },
|
||||
"PTT: RIGHT BUTTON"
|
||||
|
||||
Checkbox check_rxactive {
|
||||
{ 3 * 8, (27 * 8) + 4 },
|
||||
8,
|
||||
"RX audio listening",
|
||||
false
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 13 * 8, 30 * 8 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_squelch {
|
||||
{ 13 * 8, 32 * 8 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Text text_ptt {
|
||||
{ 7 * 8, 35 * 8, 16 * 8, 16 },
|
||||
"PTT: RIGHT BUTTON"
|
||||
};
|
||||
|
||||
|
||||
MessageHandlerRegistration message_handler_lcd_sync {
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->update_vumeter();
|
||||
this->do_timing();
|
||||
this->update_vumeter();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -22,10 +22,6 @@
|
||||
|
||||
#include "ui_scanner.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
@ -38,6 +34,10 @@ ScannerThread::ScannerThread(
|
||||
}
|
||||
|
||||
ScannerThread::~ScannerThread() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void ScannerThread::stop() {
|
||||
if( thread ) {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
@ -49,6 +49,28 @@ void ScannerThread::set_scanning(const bool v) {
|
||||
_scanning = v;
|
||||
}
|
||||
|
||||
bool ScannerThread::is_scanning() {
|
||||
return _scanning;
|
||||
}
|
||||
|
||||
void ScannerThread::set_freq_lock(const uint32_t v) {
|
||||
_freq_lock = v;
|
||||
}
|
||||
|
||||
uint32_t ScannerThread::is_freq_lock() {
|
||||
return _freq_lock;
|
||||
}
|
||||
|
||||
void ScannerThread::set_freq_del(const uint32_t v) {
|
||||
_freq_del = v;
|
||||
}
|
||||
|
||||
void ScannerThread::change_scanning_direction() {
|
||||
_fwd = !_fwd;
|
||||
chThdSleepMilliseconds(300); //Give some pause after reversing scanning direction
|
||||
|
||||
}
|
||||
|
||||
msg_t ScannerThread::static_fn(void* arg) {
|
||||
auto obj = static_cast<ScannerThread*>(arg);
|
||||
obj->run();
|
||||
@ -56,36 +78,78 @@ msg_t ScannerThread::static_fn(void* arg) {
|
||||
}
|
||||
|
||||
void ScannerThread::run() {
|
||||
RetuneMessage message { };
|
||||
uint32_t frequency_index = 0;
|
||||
|
||||
while( !chThdShouldTerminate() ) {
|
||||
if (_scanning) {
|
||||
// Retune
|
||||
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]);
|
||||
|
||||
message.range = frequency_index;
|
||||
EventDispatcher::send_message(message);
|
||||
|
||||
|
||||
frequency_index++;
|
||||
if (frequency_index >= frequency_list_.size())
|
||||
frequency_index = 0;
|
||||
if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ...
|
||||
RetuneMessage message { };
|
||||
uint32_t frequency_index = frequency_list_.size();
|
||||
bool restart_scan = false; //Flag whenever scanning is restarting after a pause
|
||||
while( !chThdShouldTerminate() ) {
|
||||
if (_scanning) { //Scanning
|
||||
if (_freq_lock == 0) { //normal scanning (not performing freq_lock)
|
||||
if (!restart_scan) { //looping at full speed
|
||||
if (_fwd) { //forward
|
||||
frequency_index++;
|
||||
if (frequency_index >= frequency_list_.size())
|
||||
frequency_index = 0;
|
||||
|
||||
} else { //reverse
|
||||
if (frequency_index < 1)
|
||||
frequency_index = frequency_list_.size();
|
||||
frequency_index--;
|
||||
}
|
||||
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); // Retune
|
||||
}
|
||||
else
|
||||
restart_scan=false; //Effectively skipping first retuning, giving system time
|
||||
}
|
||||
message.range = frequency_index; //Inform freq (for coloring purposes also!)
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
else { //NOT scanning
|
||||
if (_freq_del != 0) { //There is a frequency to delete
|
||||
for (uint16_t i = 0; i < frequency_list_.size(); i++) { //Search for the freq to delete
|
||||
if (frequency_list_[i] == _freq_del)
|
||||
{ //found: Erase it
|
||||
frequency_list_.erase(frequency_list_.begin() + i);
|
||||
if (i==0) //set scan index one place back to compensate
|
||||
i=frequency_list_.size();
|
||||
else
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_freq_del = 0; //deleted.
|
||||
}
|
||||
else {
|
||||
restart_scan=true; //Flag the need for skipping a cycle when restarting scan
|
||||
}
|
||||
}
|
||||
chThdSleepMilliseconds(50); //Needed to (eventually) stabilize the receiver into new freq
|
||||
}
|
||||
|
||||
chThdSleepMilliseconds(50);
|
||||
}
|
||||
}
|
||||
|
||||
void ScannerView::handle_retune(uint32_t i) {
|
||||
text_cycle.set( to_string_dec_uint(i) + "/" +
|
||||
to_string_dec_uint(frequency_list.size()) + " : " +
|
||||
to_string_dec_uint(frequency_list[i]) );
|
||||
desc_cycle.set( description_list[i] );
|
||||
switch (scan_thread->is_freq_lock())
|
||||
{
|
||||
case 0: //NO FREQ LOCK, ONGOING STANDARD SCANNING
|
||||
text_cycle.set( to_string_dec_uint(i + 1,3) );
|
||||
current_index = i; //since it is an ongoing scan, this is a new index
|
||||
if (description_list[current_index].size() > 0) desc_cycle.set( description_list[current_index] ); //Show new description
|
||||
break;
|
||||
case 1: //STARTING LOCK FREQ
|
||||
big_display.set_style(&style_yellow);
|
||||
break;
|
||||
case MAX_FREQ_LOCK: //FREQ IS STRONG: GREEN and scanner will pause when on_statistics_update()
|
||||
big_display.set_style(&style_green);
|
||||
break;
|
||||
default: //freq lock is checking the signal, do not update display
|
||||
return;
|
||||
}
|
||||
big_display.set(frequency_list[current_index]); //UPDATE the big Freq after 0, 1 or MAX_FREQ_LOCK (at least, for color synching)
|
||||
}
|
||||
|
||||
void ScannerView::focus() {
|
||||
field_lna.focus();
|
||||
field_mode.focus();
|
||||
}
|
||||
|
||||
ScannerView::~ScannerView() {
|
||||
@ -94,9 +158,20 @@ ScannerView::~ScannerView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void ScannerView::show_max() { //show total number of freqs to scan
|
||||
if (frequency_list.size() == MAX_DB_ENTRY) {
|
||||
text_max.set_style(&style_red);
|
||||
text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)");
|
||||
}
|
||||
else {
|
||||
text_max.set_style(&style_grey);
|
||||
text_max.set( "/ " + to_string_dec_uint(frequency_list.size()));
|
||||
}
|
||||
}
|
||||
|
||||
ScannerView::ScannerView(
|
||||
NavigationView&
|
||||
)
|
||||
NavigationView& nav
|
||||
) : nav_ { nav }
|
||||
{
|
||||
add_children({
|
||||
&labels,
|
||||
@ -105,110 +180,340 @@ ScannerView::ScannerView(
|
||||
&field_rf_amp,
|
||||
&field_volume,
|
||||
&field_bw,
|
||||
&field_trigger,
|
||||
&field_squelch,
|
||||
&field_wait,
|
||||
//&record_view,
|
||||
&rssi,
|
||||
&text_cycle,
|
||||
&text_max,
|
||||
&desc_cycle,
|
||||
//&waterfall,
|
||||
&big_display,
|
||||
&button_manual_start,
|
||||
&button_manual_end,
|
||||
&field_mode,
|
||||
&step_mode,
|
||||
&button_manual_scan,
|
||||
&button_pause,
|
||||
&button_dir,
|
||||
&button_audio_app,
|
||||
&button_mic_app,
|
||||
&button_add,
|
||||
&button_remove
|
||||
|
||||
});
|
||||
|
||||
std::string scanner_file = "SCANNER";
|
||||
if (load_freqman_file(scanner_file, database)) {
|
||||
for(auto& entry : database) {
|
||||
// FIXME
|
||||
if (entry.type == RANGE) {
|
||||
for (uint32_t i=entry.frequency_a; i < entry.frequency_b; i+= 1000000) {
|
||||
frequency_list.push_back(i);
|
||||
description_list.push_back("RNG " + to_string_dec_uint(entry.frequency_a) + ">" + to_string_dec_uint(entry.frequency_b));
|
||||
}
|
||||
} else {
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back(entry.description);
|
||||
}
|
||||
def_step = change_mode(AM); //Start on AM
|
||||
field_mode.set_by_value(AM); //Reflect the mode into the manual selector
|
||||
|
||||
//HELPER: Pre-setting a manual range, based on stored frequency
|
||||
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
|
||||
frequency_range.min = stored_freq - 1000000;
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
frequency_range.max = stored_freq + 1000000;
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
|
||||
button_manual_start.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
frequency_range.min = f;
|
||||
button_manual_start.set_text(to_string_short_freq(f));
|
||||
};
|
||||
};
|
||||
|
||||
button_manual_end.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(frequency_range.max);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
frequency_range.max = f;
|
||||
button_manual_end.set_text(to_string_short_freq(f));
|
||||
};
|
||||
};
|
||||
|
||||
button_pause.on_select = [this](Button&) {
|
||||
if ( userpause )
|
||||
user_resume();
|
||||
else {
|
||||
scan_pause();
|
||||
button_pause.set_text("RESUME"); //PAUSED, show resume
|
||||
userpause=true;
|
||||
}
|
||||
} else {
|
||||
// DEBUG
|
||||
// TODO: Clean this
|
||||
frequency_list.push_back(466025000);
|
||||
description_list.push_back("POCSAG-France");
|
||||
frequency_list.push_back(466050000);
|
||||
description_list.push_back("POCSAG-France");
|
||||
frequency_list.push_back(466075000);
|
||||
description_list.push_back("POCSAG-France");
|
||||
frequency_list.push_back(466175000);
|
||||
description_list.push_back("POCSAG-France");
|
||||
frequency_list.push_back(466206250);
|
||||
description_list.push_back("POCSAG-France");
|
||||
frequency_list.push_back(466231250);
|
||||
description_list.push_back("POCSAG-France");
|
||||
}
|
||||
|
||||
field_bw.set_selected_index(2);
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) {
|
||||
receiver_model.set_nbfm_configuration(n);
|
||||
};
|
||||
|
||||
field_wait.on_change = [this](int32_t v) {
|
||||
wait = v;
|
||||
};
|
||||
field_wait.set_value(5);
|
||||
|
||||
field_trigger.on_change = [this](int32_t v) {
|
||||
trigger = v;
|
||||
};
|
||||
field_trigger.set_value(30);
|
||||
|
||||
field_squelch.set_value(receiver_model.squelch_level());
|
||||
field_squelch.on_change = [this](int32_t v) {
|
||||
squelch = v;
|
||||
receiver_model.set_squelch_level(v);
|
||||
button_audio_app.on_select = [this](Button&) {
|
||||
scan_thread->stop();
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>();
|
||||
};
|
||||
|
||||
button_mic_app.on_select = [this](Button&) {
|
||||
scan_thread->stop();
|
||||
nav_.pop();
|
||||
nav_.push<MicTXView>();
|
||||
};
|
||||
|
||||
button_remove.on_select = [this](Button&) {
|
||||
if (frequency_list.size() > current_index) {
|
||||
if (scan_thread->is_scanning()) //STOP Scanning if necessary
|
||||
scan_thread->set_scanning(false);
|
||||
scan_thread->set_freq_del(frequency_list[current_index]);
|
||||
description_list.erase(description_list.begin() + current_index);
|
||||
frequency_list.erase(frequency_list.begin() + current_index);
|
||||
show_max(); //UPDATE new list size on screen
|
||||
desc_cycle.set(" "); //Clean up description (cosmetic detail)
|
||||
scan_thread->set_freq_lock(0); //Reset the scanner lock
|
||||
if ( userpause ) //If user-paused, resume
|
||||
user_resume();
|
||||
}
|
||||
};
|
||||
|
||||
button_manual_scan.on_select = [this](Button&) {
|
||||
if (!frequency_range.min || !frequency_range.max) {
|
||||
nav_.display_modal("Error", "Both START and END freqs\nneed a value");
|
||||
} else if (frequency_range.min > frequency_range.max) {
|
||||
nav_.display_modal("Error", "END freq\nis lower than START");
|
||||
} else {
|
||||
audio::output::stop();
|
||||
scan_thread->stop(); //STOP SCANNER THREAD
|
||||
frequency_list.clear();
|
||||
description_list.clear();
|
||||
def_step = step_mode.selected_index_value(); //Use def_step from manual selector
|
||||
|
||||
description_list.push_back(
|
||||
"M:" + to_string_short_freq(frequency_range.min) + " >"
|
||||
+ to_string_short_freq(frequency_range.max) + " S:"
|
||||
+ to_string_short_freq(def_step)
|
||||
);
|
||||
|
||||
rf::Frequency frequency = frequency_range.min;
|
||||
while (frequency_list.size() < MAX_DB_ENTRY && frequency <= frequency_range.max) { //add manual range
|
||||
frequency_list.push_back(frequency);
|
||||
description_list.push_back(""); //If empty, will keep showing the last description
|
||||
frequency+=def_step;
|
||||
}
|
||||
show_max();
|
||||
if ( userpause ) //If user-paused, resume
|
||||
user_resume();
|
||||
big_display.set_style(&style_grey); //Back to grey color
|
||||
start_scan_thread(); //RESTART SCANNER THREAD
|
||||
}
|
||||
};
|
||||
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
change_mode(v);
|
||||
if ( !scan_thread->is_scanning() ) //for some motive, audio output gets stopped.
|
||||
audio::output::start(); //So if scan was stopped we resume audio
|
||||
receiver_model.enable();
|
||||
};
|
||||
|
||||
button_dir.on_select = [this](Button&) {
|
||||
scan_thread->change_scanning_direction();
|
||||
if ( userpause ) //If user-paused, resume
|
||||
user_resume();
|
||||
big_display.set_style(&style_grey); //Back to grey color
|
||||
};
|
||||
|
||||
button_add.on_select = [this](Button&) { //frequency_list[current_index]
|
||||
File scanner_file;
|
||||
auto result = scanner_file.open("FREQMAN/SCANNER.TXT"); //First search if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
std::string frequency_to_add = "f="
|
||||
+ to_string_dec_uint(frequency_list[current_index] / 1000)
|
||||
+ to_string_dec_uint(frequency_list[current_index] % 1000UL, 3, '0');
|
||||
char one_char[1]; //Read it char by char
|
||||
std::string line; //and put read line in here
|
||||
bool found=false;
|
||||
for (size_t pointer=0; pointer < scanner_file.size();pointer++) {
|
||||
|
||||
scanner_file.seek(pointer);
|
||||
scanner_file.read(one_char, 1);
|
||||
if ((int)one_char[0] > 31) { //ascii space upwards
|
||||
line += one_char[0]; //Add it to the textline
|
||||
}
|
||||
else if (one_char[0] == '\n') { //New Line
|
||||
if (line.compare(0, frequency_to_add.size(),frequency_to_add) == 0) {
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
line.clear(); //Ready for next textline
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
nav_.display_modal("Error", "Frequency already exists");
|
||||
big_display.set(frequency_list[current_index]); //After showing an error
|
||||
}
|
||||
else {
|
||||
auto result = scanner_file.append("FREQMAN/SCANNER.TXT"); //Second: append if it is not there
|
||||
scanner_file.write_line(frequency_to_add + ",d=ADD FQ");
|
||||
}
|
||||
} else
|
||||
{
|
||||
nav_.display_modal("Error", "Cannot open SCANNER.TXT\nfor appending freq.");
|
||||
big_display.set(frequency_list[current_index]); //After showing an error
|
||||
}
|
||||
};
|
||||
|
||||
//PRE-CONFIGURATION:
|
||||
field_wait.on_change = [this](int32_t v) { wait = v; }; field_wait.set_value(5);
|
||||
field_squelch.on_change = [this](int32_t v) { squelch = v; }; field_squelch.set_value(-10);
|
||||
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
|
||||
field_volume.on_change = [this](int32_t v) {
|
||||
this->on_headphone_volume_changed(v);
|
||||
};
|
||||
|
||||
audio::output::start();
|
||||
|
||||
audio::output::mute();
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.enable();
|
||||
receiver_model.set_squelch_level(0);
|
||||
receiver_model.set_nbfm_configuration(field_bw.selected_index());
|
||||
audio::output::unmute();
|
||||
|
||||
// TODO: Scanning thread here
|
||||
scan_thread = std::make_unique<ScannerThread>(frequency_list);
|
||||
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
|
||||
// LEARN FREQUENCIES
|
||||
std::string scanner_txt = "SCANNER";
|
||||
if ( load_freqman_file(scanner_txt, database) ) {
|
||||
for(auto& entry : database) { // READ LINE PER LINE
|
||||
if (frequency_list.size() < MAX_DB_ENTRY) { //We got space!
|
||||
if (entry.type == RANGE) { //RANGE
|
||||
switch (entry.step) {
|
||||
case AM_US: def_step = 10000; break ;
|
||||
case AM_EUR:def_step = 9000; break ;
|
||||
case NFM_1: def_step = 12500; break ;
|
||||
case NFM_2: def_step = 6250; break ;
|
||||
case FM_1: def_step = 100000; break ;
|
||||
case FM_2: def_step = 50000; break ;
|
||||
case N_1: def_step = 25000; break ;
|
||||
case N_2: def_step = 250000; break ;
|
||||
case AIRBAND:def_step= 8330; break ;
|
||||
}
|
||||
frequency_list.push_back(entry.frequency_a); //Store starting freq and description
|
||||
description_list.push_back("R:" + to_string_short_freq(entry.frequency_a)
|
||||
+ " >" + to_string_short_freq(entry.frequency_b)
|
||||
+ " S:" + to_string_short_freq(def_step));
|
||||
while (frequency_list.size() < MAX_DB_ENTRY && entry.frequency_a <= entry.frequency_b) { //add the rest of the range
|
||||
entry.frequency_a+=def_step;
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back(""); //Token (keep showing the last description)
|
||||
}
|
||||
} else if ( entry.type == SINGLE) {
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back("S: " + entry.description);
|
||||
}
|
||||
show_max();
|
||||
}
|
||||
else
|
||||
{
|
||||
break; //No more space: Stop reading the txt file !
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
desc_cycle.set(" NO SCANNER.TXT FILE ..." );
|
||||
}
|
||||
audio::output::stop();
|
||||
step_mode.set_by_value(def_step); //Impose the default step into the manual step selector
|
||||
start_scan_thread();
|
||||
}
|
||||
|
||||
void ScannerView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
int32_t max_db = statistics.max_db;
|
||||
|
||||
if (timer <= wait)
|
||||
timer++;
|
||||
|
||||
if (max_db < -trigger) {
|
||||
if (timer == wait) {
|
||||
//audio::output::stop();
|
||||
scan_thread->set_scanning(true);
|
||||
if ( !userpause ) //Scanning not user-paused
|
||||
{
|
||||
if (timer >= (wait * 10) )
|
||||
{
|
||||
timer = 0;
|
||||
scan_resume();
|
||||
}
|
||||
else if (!timer)
|
||||
{
|
||||
if (statistics.max_db > squelch ) { //There is something on the air...(statistics.max_db > -squelch)
|
||||
if (scan_thread->is_freq_lock() >= MAX_FREQ_LOCK) { //checking time reached
|
||||
scan_pause();
|
||||
timer++;
|
||||
} else {
|
||||
scan_thread->set_freq_lock( scan_thread->is_freq_lock() + 1 ); //in lock period, still analyzing the signal
|
||||
}
|
||||
} else { //There is NOTHING on the air
|
||||
if (scan_thread->is_freq_lock() > 0) { //But are we already in freq_lock ?
|
||||
big_display.set_style(&style_grey); //Back to grey color
|
||||
scan_thread->set_freq_lock(0); //Reset the scanner lock, since there is no signal
|
||||
}
|
||||
}
|
||||
}
|
||||
else //Ongoing wait time
|
||||
{
|
||||
timer++;
|
||||
}
|
||||
} else {
|
||||
//audio::output::start();
|
||||
scan_thread->set_scanning(false);
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ScannerView::scan_pause() {
|
||||
if (scan_thread->is_scanning()) {
|
||||
scan_thread->set_freq_lock(0); //Reset the scanner lock (because user paused, or MAX_FREQ_LOCK reached) for next freq scan
|
||||
scan_thread->set_scanning(false); // WE STOP SCANNING
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
void ScannerView::scan_resume() {
|
||||
audio::output::stop();
|
||||
big_display.set_style(&style_grey); //Back to grey color
|
||||
if (!scan_thread->is_scanning())
|
||||
scan_thread->set_scanning(true); // RESUME!
|
||||
}
|
||||
|
||||
void ScannerView::user_resume() {
|
||||
timer = wait * 10; //Will trigger a scan_resume() on_statistics_update, also advancing to next freq.
|
||||
button_pause.set_text("PAUSE"); //Show button for pause
|
||||
userpause=false; //Resume scanning
|
||||
}
|
||||
|
||||
void ScannerView::on_headphone_volume_changed(int32_t v) {
|
||||
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
|
||||
receiver_model.set_headphone_volume(new_volume);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thread->stop(); After this do a start_scan_thread()
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
options_t bw;
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { };
|
||||
|
||||
switch (new_mod) {
|
||||
case NFM: //bw 16k (2) default
|
||||
bw.emplace_back("8k5", 0);
|
||||
bw.emplace_back("11k", 0);
|
||||
bw.emplace_back("16k", 0);
|
||||
field_bw.set_options(bw);
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
field_bw.set_selected_index(2);
|
||||
receiver_model.set_nbfm_configuration(field_bw.selected_index());
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
|
||||
receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000);
|
||||
break;
|
||||
case AM:
|
||||
bw.emplace_back("DSB", 0);
|
||||
bw.emplace_back("USB", 0);
|
||||
bw.emplace_back("LSB", 0);
|
||||
field_bw.set_options(bw);
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
||||
field_bw.set_selected_index(0);
|
||||
receiver_model.set_am_configuration(field_bw.selected_index());
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
|
||||
receiver_model.set_sampling_rate(2000000);receiver_model.set_baseband_bandwidth(2000000);
|
||||
break;
|
||||
case WFM:
|
||||
bw.emplace_back("16k", 0);
|
||||
field_bw.set_options(bw);
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
field_bw.set_selected_index(0);
|
||||
receiver_model.set_wfm_configuration(field_bw.selected_index());
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
|
||||
receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(2000000);
|
||||
break;
|
||||
}
|
||||
|
||||
return mod_step[new_mod];
|
||||
}
|
||||
|
||||
void ScannerView::start_scan_thread() {
|
||||
receiver_model.enable();
|
||||
receiver_model.set_squelch_level(0);
|
||||
scan_thread = std::make_unique<ScannerThread>(frequency_list);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
@ -20,20 +20,46 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
|
||||
#define MAX_DB_ENTRY 500
|
||||
#define MAX_FREQ_LOCK 10 //50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous
|
||||
|
||||
namespace ui {
|
||||
|
||||
enum modulation_type { AM = 0,WFM,NFM };
|
||||
|
||||
string const mod_name[3] = {"AM", "WFM", "NFM"};
|
||||
size_t const mod_step[3] = {9000, 100000, 12500 };
|
||||
|
||||
class ScannerThread {
|
||||
public:
|
||||
ScannerThread(std::vector<rf::Frequency> frequency_list);
|
||||
~ScannerThread();
|
||||
|
||||
|
||||
void set_scanning(const bool v);
|
||||
bool is_scanning();
|
||||
|
||||
void set_freq_lock(const uint32_t v);
|
||||
uint32_t is_freq_lock();
|
||||
|
||||
void set_freq_del(const uint32_t v);
|
||||
|
||||
void change_scanning_direction();
|
||||
|
||||
void stop();
|
||||
|
||||
ScannerThread(const ScannerThread&) = delete;
|
||||
ScannerThread(ScannerThread&&) = delete;
|
||||
@ -45,38 +71,81 @@ private:
|
||||
Thread* thread { nullptr };
|
||||
|
||||
bool _scanning { true };
|
||||
|
||||
bool _fwd { true };
|
||||
uint32_t _freq_lock { 0 };
|
||||
uint32_t _freq_del { 0 };
|
||||
static msg_t static_fn(void* arg);
|
||||
|
||||
void run();
|
||||
};
|
||||
|
||||
class ScannerView : public View {
|
||||
public:
|
||||
ScannerView(NavigationView&);
|
||||
ScannerView(NavigationView& nav);
|
||||
~ScannerView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq(rf::Frequency f);
|
||||
|
||||
const Style style_grey { // scanning
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
|
||||
std::string title() const override { return "Scanner"; };
|
||||
const Style style_yellow { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::dark_yellow(),
|
||||
};
|
||||
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
std::string title() const override { return "SCANNER"; };
|
||||
std::vector<rf::Frequency> frequency_list{ };
|
||||
std::vector<string> description_list { };
|
||||
|
||||
//void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void start_scan_thread();
|
||||
size_t change_mode(uint8_t mod_type);
|
||||
void show_max();
|
||||
void scan_pause();
|
||||
void scan_resume();
|
||||
void user_resume();
|
||||
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
void handle_retune(uint32_t i);
|
||||
|
||||
std::vector<rf::Frequency> frequency_list { };
|
||||
std::vector<string> description_list { };
|
||||
int32_t trigger { 0 };
|
||||
|
||||
jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual scan task too...
|
||||
int32_t squelch { 0 };
|
||||
uint32_t timer { 0 };
|
||||
uint32_t wait { 0 };
|
||||
size_t def_step { 0 };
|
||||
freqman_db database { };
|
||||
uint32_t current_index { 0 };
|
||||
bool userpause { false };
|
||||
|
||||
Labels labels {
|
||||
{ { 0 * 8, 0 * 16 }, "LNA: TRIGGER: /99 VOL:", Color::light_grey() },
|
||||
{ { 0 * 8, 1 * 16 }, "VGA: SQUELCH: /99 AMP:", Color::light_grey() },
|
||||
{ { 0 * 8, 2 * 16 }, " BW: WAIT:", Color::light_grey() },
|
||||
{ { 0 * 8, 0 * 16 }, "LNA: VGA: AMP: VOL:", Color::light_grey() },
|
||||
{ { 0 * 8, 1* 16 }, "BW: SQUELCH: db WAIT:", Color::light_grey() },
|
||||
{ { 3 * 8, 10 * 16 }, "START END MANUAL", Color::light_grey() },
|
||||
{ { 0 * 8, (26 * 8) + 4 }, "MODE:", Color::light_grey() },
|
||||
{ { 11 * 8, (26 * 8) + 4 }, "STEP:", Color::light_grey() },
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
@ -84,15 +153,15 @@ private:
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 4 * 8, 1 * 16 }
|
||||
{ 11 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 28 * 8, 1 * 16 }
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
NumberField field_volume {
|
||||
{ 28 * 8, 0 * 16 },
|
||||
{ 24 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
@ -100,46 +169,118 @@ private:
|
||||
};
|
||||
|
||||
OptionsField field_bw {
|
||||
{ 4 * 8, 2 * 16 },
|
||||
3,
|
||||
{
|
||||
{ "8k5", 0 },
|
||||
{ "11k", 0 },
|
||||
{ "16k", 0 },
|
||||
}
|
||||
};
|
||||
{ 3 * 8, 1 * 16 },
|
||||
4,
|
||||
{ }
|
||||
};
|
||||
|
||||
NumberField field_trigger {
|
||||
{ 16 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_squelch {
|
||||
{ 16 * 8, 1 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
{ 15 * 8, 1 * 16 },
|
||||
3,
|
||||
{ -90, 20 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_wait {
|
||||
{ 16 * 8, 2 * 16 },
|
||||
{ 26 * 8, 1 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
RSSI rssi {
|
||||
{ 0 * 16, 2 * 16, 15 * 16, 8 },
|
||||
};
|
||||
|
||||
Text text_cycle {
|
||||
{ 0, 5 * 16, 240, 16 },
|
||||
"--/--"
|
||||
{ 0, 3 * 16, 3 * 8, 16 },
|
||||
};
|
||||
|
||||
Text text_max {
|
||||
{ 4 * 8, 3 * 16, 18 * 8, 16 },
|
||||
};
|
||||
|
||||
Text desc_cycle {
|
||||
{0, 6 * 16, 240, 16 },
|
||||
" "
|
||||
{0, 4 * 16, 240, 16 },
|
||||
};
|
||||
|
||||
BigFrequency big_display { //Show frequency in glamour
|
||||
{ 4, 6 * 16, 28 * 8, 52 },
|
||||
0
|
||||
};
|
||||
|
||||
Button button_manual_start {
|
||||
{ 0 * 8, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
|
||||
Button button_manual_end {
|
||||
{ 12 * 8, 11 * 16, 11 * 8, 28 },
|
||||
""
|
||||
};
|
||||
|
||||
Button button_manual_scan {
|
||||
{ 24 * 8, 11 * 16, 6 * 8, 28 },
|
||||
"SCAN"
|
||||
};
|
||||
|
||||
OptionsField field_mode {
|
||||
{ 5 * 8, (26 * 8) + 4 },
|
||||
6,
|
||||
{
|
||||
{ " AM ", 0 },
|
||||
{ " WFM ", 1 },
|
||||
{ " NFM ", 2 },
|
||||
}
|
||||
};
|
||||
|
||||
OptionsField step_mode {
|
||||
{ 17 * 8, (26 * 8) + 4 },
|
||||
12,
|
||||
{
|
||||
{ "5Khz (SA AM)", 5000 },
|
||||
{ "9Khz (EU AM)", 9000 },
|
||||
{ "10Khz(US AM)", 10000 },
|
||||
{ "50Khz (FM1)", 50000 },
|
||||
{ "100Khz(FM2)", 100000 },
|
||||
{ "6.25khz(NFM)", 6250 },
|
||||
{ "12.5khz(NFM)", 12500 },
|
||||
{ "25khz (N1)", 25000 },
|
||||
{ "250khz (N2)", 250000 },
|
||||
{ "8.33khz(AIR)", 8330 }
|
||||
}
|
||||
};
|
||||
|
||||
Button button_pause {
|
||||
{ 0, (15 * 16) - 4, 72, 28 },
|
||||
"PAUSE"
|
||||
};
|
||||
|
||||
Button button_dir {
|
||||
{ 0, (35 * 8) - 4, 72, 28 },
|
||||
"FW><RV"
|
||||
};
|
||||
|
||||
Button button_audio_app {
|
||||
{ 84, (15 * 16) - 4, 72, 28 },
|
||||
"AUDIO"
|
||||
};
|
||||
|
||||
Button button_mic_app {
|
||||
{ 84, (35 * 8) - 4, 72, 28 },
|
||||
"MIC TX"
|
||||
};
|
||||
|
||||
Button button_add {
|
||||
{ 168, (15 * 16) - 4, 72, 28 },
|
||||
"ADD FQ"
|
||||
};
|
||||
|
||||
Button button_remove {
|
||||
{ 168, (35 * 8) - 4, 72, 28 },
|
||||
"DEL FQ"
|
||||
};
|
||||
|
||||
std::unique_ptr<ScannerThread> scan_thread { };
|
||||
@ -160,4 +301,4 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
@ -54,7 +54,7 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
});
|
||||
|
||||
field_frequency.set_value(target_frequency_);
|
||||
field_frequency.set_step(10000);
|
||||
field_frequency.set_step(500); //euquiq: was 10000, but we are using this for fine-tunning
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
set_target_frequency(f);
|
||||
field_frequency.set_value(f);
|
||||
@ -86,12 +86,12 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
nav.push<GeoMapView>(
|
||||
"",
|
||||
altitude,
|
||||
sonde_id,
|
||||
gps_info.alt,
|
||||
GeoPos::alt_unit::METERS,
|
||||
latitude,
|
||||
longitude,
|
||||
0);
|
||||
gps_info.lat,
|
||||
gps_info.lon,
|
||||
999); //set a dummy heading out of range to draw a cross...probably not ideal?
|
||||
};
|
||||
|
||||
logger = std::make_unique<SondeLogger>();
|
||||
@ -113,16 +113,15 @@ void SondeView::on_packet(const sonde::Packet& packet) {
|
||||
//const auto hex_formatted = packet.symbols_formatted();
|
||||
|
||||
text_signature.set(packet.type_string());
|
||||
text_serial.set(packet.serial_number());
|
||||
sonde_id = packet.serial_number(); //used also as tag on the geomap
|
||||
text_serial.set(sonde_id);
|
||||
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 3) + "V");
|
||||
|
||||
gps_info = packet.get_GPS_data();
|
||||
|
||||
altitude = packet.GPS_altitude();
|
||||
latitude = packet.GPS_latitude();
|
||||
longitude = packet.GPS_longitude();
|
||||
|
||||
geopos.set_altitude(altitude);
|
||||
geopos.set_lat(latitude);
|
||||
geopos.set_lon(longitude);
|
||||
geopos.set_altitude(gps_info.alt);
|
||||
geopos.set_lat(gps_info.lat);
|
||||
geopos.set_lon(gps_info.lon);
|
||||
|
||||
if (logger && logging) {
|
||||
logger->on_packet(packet);
|
||||
|
@ -65,11 +65,10 @@ public:
|
||||
|
||||
private:
|
||||
std::unique_ptr<SondeLogger> logger { };
|
||||
uint32_t target_frequency_ { 402000000 };
|
||||
uint32_t target_frequency_ { 402700000 };
|
||||
bool logging { false };
|
||||
int32_t altitude { 0 };
|
||||
float latitude { 0 };
|
||||
float longitude { 0 };
|
||||
sonde::GPS_data gps_info;
|
||||
std::string sonde_id;
|
||||
|
||||
Labels labels {
|
||||
{ { 0 * 8, 2 * 16 }, "Signature:", Color::light_grey() },
|
||||
|
@ -81,15 +81,6 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
int16_t sample;
|
||||
uint32_t average;
|
||||
|
||||
if (!wav_reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Couldn't open file.", INFO, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.", INFO, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
text_filename.set(file_path.filename().string());
|
||||
auto ms_duration = wav_reader->ms_duration();
|
||||
@ -148,10 +139,18 @@ ViewWavView::ViewWavView(
|
||||
&field_cursor_b,
|
||||
&text_delta
|
||||
});
|
||||
|
||||
reset_controls();
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".WAV");
|
||||
open_view->on_changed = [this](std::filesystem::path file_path) {
|
||||
if (!wav_reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Couldn't open file.", INFO, nullptr);
|
||||
return;
|
||||
}
|
||||
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.", INFO, nullptr);
|
||||
return;
|
||||
}
|
||||
load_wav(file_path);
|
||||
field_pos_seconds.focus();
|
||||
};
|
||||
@ -176,7 +175,6 @@ ViewWavView::ViewWavView(
|
||||
refresh_measurements();
|
||||
};
|
||||
|
||||
reset_controls();
|
||||
}
|
||||
|
||||
void ViewWavView::focus() {
|
||||
|
@ -48,11 +48,28 @@ enum freqman_entry_type {
|
||||
RANGE
|
||||
};
|
||||
|
||||
//Entry step placed for AlainD freqman version (or any other enhanced version)
|
||||
enum freqman_entry_step {
|
||||
STEP_DEF = 0, // default
|
||||
AM_US, // 10 Khz AM/CB
|
||||
AM_EUR, // 9 Khz LW/MW
|
||||
NFM_1, // 12,5 Khz (Analogic PMR 446)
|
||||
NFM_2, // 6,25 Khz (Digital PMR 446)
|
||||
FM_1, // 100 Khz
|
||||
FM_2, // 50 Khz
|
||||
N_1, // 25 Khz
|
||||
N_2, // 250 Khz
|
||||
AIRBAND, // AIRBAND 8,33 Khz
|
||||
ERROR_STEP
|
||||
};
|
||||
|
||||
// freqman_entry_step step added, as above, to provide compatibility / future enhancement.
|
||||
struct freqman_entry {
|
||||
rf::Frequency frequency_a { 0 };
|
||||
rf::Frequency frequency_b { 0 };
|
||||
std::string description { };
|
||||
freqman_entry_type type { };
|
||||
freqman_entry_step step { };
|
||||
};
|
||||
|
||||
using freqman_db = std::vector<freqman_entry>;
|
||||
|
@ -113,7 +113,8 @@ std::string to_string_dec_int(
|
||||
}
|
||||
|
||||
std::string to_string_short_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int((f / 100) % 10000, 4, '0');
|
||||
//was... to_string_dec_int(f / 1000000,4)
|
||||
auto final_str = to_string_dec_int(f / 1000000) + "." + to_string_dec_int((f / 100) % 10000, 4, '0');
|
||||
return final_str;
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,15 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
|
||||
update_items();
|
||||
}
|
||||
|
||||
void BtnGridView::set_arrow_enabled(bool new_value) {
|
||||
if(new_value){
|
||||
add_child(&arrow_more);
|
||||
}
|
||||
else{
|
||||
remove_child(&arrow_more);
|
||||
}
|
||||
};
|
||||
|
||||
void BtnGridView::on_tick_second() {
|
||||
if (more && blink)
|
||||
arrow_more.set_foreground(Color::white());
|
||||
|
@ -62,6 +62,7 @@ public:
|
||||
uint32_t highlighted_index();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void set_arrow_enabled(bool new_value);
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent event) override;
|
||||
|
@ -59,7 +59,7 @@ GeoPos::GeoPos(
|
||||
set_altitude(0);
|
||||
set_lat(0);
|
||||
set_lon(0);
|
||||
|
||||
|
||||
const auto changed_fn = [this](int32_t) {
|
||||
float lat_value = lat();
|
||||
float lon_value = lon();
|
||||
@ -163,15 +163,22 @@ void GeoMap::paint(Painter& painter) {
|
||||
prev_x_pos = x_pos;
|
||||
prev_y_pos = y_pos;
|
||||
}
|
||||
|
||||
//center tag above point
|
||||
if(tag_.find_first_not_of(' ') != tag_.npos){ //only draw tag if we have something other than spaces
|
||||
painter.draw_string(r.center() - Point(((int)tag_.length() * 8 / 2), 2 * 16), style(), tag_);
|
||||
}
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
display.fill_rectangle({ r.center() - Point(16, 1), { 32, 2 } }, Color::red());
|
||||
display.fill_rectangle({ r.center() - Point(1, 16), { 2, 32 } }, Color::red());
|
||||
} else {
|
||||
} else if (angle_ < 360){
|
||||
//if we have a valid angle draw bearing
|
||||
draw_bearing(r.center(), angle_, 10, Color::red());
|
||||
//center tag above bearing
|
||||
painter.draw_string(r.center() - Point(((int)tag_.length() * 8 / 2), 2 * 16), style(), tag_);
|
||||
}
|
||||
else {
|
||||
//draw a small cross
|
||||
display.fill_rectangle({ r.center() - Point(8, 1), { 16, 2 } }, Color::red());
|
||||
display.fill_rectangle({ r.center() - Point(1, 8), { 2, 16 } }, Color::red());
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +238,7 @@ void GeoMap::set_mode(GeoMapMode mode) {
|
||||
mode_ = mode;
|
||||
}
|
||||
|
||||
void GeoMap::draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color) {
|
||||
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
||||
Point arrow_a, arrow_b, arrow_c;
|
||||
|
||||
for (size_t thickness = 0; thickness < 3; thickness++) {
|
||||
@ -254,11 +261,12 @@ void GeoMapView::focus() {
|
||||
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT, nullptr);
|
||||
}
|
||||
|
||||
void GeoMapView::update_position(float lat, float lon) {
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle) {
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
geomap.set_angle(angle);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_dirty();
|
||||
}
|
||||
@ -269,7 +277,7 @@ void GeoMapView::setup() {
|
||||
geopos.set_altitude(altitude_);
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
|
||||
|
||||
geopos.on_change = [this](int32_t altitude, float lat, float lon) {
|
||||
altitude_ = altitude;
|
||||
lat_ = lat;
|
||||
@ -307,7 +315,7 @@ GeoMapView::GeoMapView(
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
float angle,
|
||||
uint16_t angle,
|
||||
const std::function<void(void)> on_close
|
||||
) : nav_ (nav),
|
||||
altitude_ (altitude),
|
||||
@ -328,6 +336,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
geomap.set_mode(mode_);
|
||||
geomap.set_tag(tag);
|
||||
geomap.set_angle(angle);
|
||||
geomap.move(lon_, lat_);
|
||||
|
||||
geopos.set_read_only(true);
|
||||
|
@ -129,8 +129,12 @@ public:
|
||||
tag_ = new_tag;
|
||||
}
|
||||
|
||||
void set_angle(uint16_t new_angle){
|
||||
angle_ = new_angle;
|
||||
}
|
||||
|
||||
private:
|
||||
void draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
|
||||
GeoMapMode mode_ { };
|
||||
File map_file { };
|
||||
@ -141,7 +145,7 @@ private:
|
||||
int32_t prev_x_pos { 0xFFFF }, prev_y_pos { 0xFFFF };
|
||||
float lat_ { };
|
||||
float lon_ { };
|
||||
float angle_ { };
|
||||
uint16_t angle_ { };
|
||||
std::string tag_ { };
|
||||
};
|
||||
|
||||
@ -154,7 +158,7 @@ public:
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
float angle,
|
||||
uint16_t angle,
|
||||
const std::function<void(void)> on_close = nullptr
|
||||
);
|
||||
GeoMapView(NavigationView& nav,
|
||||
@ -173,7 +177,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
void update_position(float lat, float lon);
|
||||
void update_position(float lat, float lon, uint16_t angle);
|
||||
|
||||
std::string title() const override { return "Map view"; };
|
||||
|
||||
@ -190,7 +194,7 @@ private:
|
||||
GeoPos::alt_unit altitude_unit_ { };
|
||||
float lat_ { };
|
||||
float lon_ { };
|
||||
float angle_ { };
|
||||
uint16_t angle_ { };
|
||||
std::function<void(void)> on_close_ { nullptr };
|
||||
|
||||
bool map_opened { };
|
||||
|
@ -30,7 +30,7 @@
|
||||
#include "bmp_modal_warning.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include "ui_about.hpp"
|
||||
#include "ui_about_simple.hpp"
|
||||
#include "ui_adsb_rx.hpp"
|
||||
#include "ui_adsb_tx.hpp"
|
||||
#include "ui_afsk_rx.hpp"
|
||||
@ -303,7 +303,36 @@ void SystemStatusView::on_camera() {
|
||||
}
|
||||
|
||||
void SystemStatusView::on_title() {
|
||||
nav_.push<AboutView>();
|
||||
if(nav_.is_top())
|
||||
nav_.push<AboutView>();
|
||||
else
|
||||
nav_.pop();
|
||||
}
|
||||
|
||||
/* Information View *****************************************************/
|
||||
|
||||
InformationView::InformationView(
|
||||
NavigationView& nav
|
||||
) : nav_ (nav)
|
||||
{
|
||||
static constexpr Style style_infobar {
|
||||
.font = font::fixed_8x16,
|
||||
.background = {33, 33, 33},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
add_children({
|
||||
&backdrop,
|
||||
&version,
|
||||
<ime
|
||||
});
|
||||
|
||||
version.set_style(&style_infobar);
|
||||
ltime.set_style(&style_infobar);
|
||||
ltime.set_seconds_enabled(true);
|
||||
ltime.set_date_enabled(false);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* Navigation ************************************************************/
|
||||
@ -416,7 +445,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
{ "Analog TV", ui::Color::yellow(), &bitmap_icon_sstv, [&nav](){ nav.push<AnalogTvView>(); } },
|
||||
{ "ERT Meter", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push<ERTAppView>(); } },
|
||||
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } },
|
||||
{ "Radiosnde", ui::Color::yellow(), &bitmap_icon_sonde, [&nav](){ nav.push<SondeView>(); } },
|
||||
{ "Radiosnde", ui::Color::green(), &bitmap_icon_sonde, [&nav](){ nav.push<SondeView>(); } },
|
||||
{ "TPMS Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.push<TPMSAppView>(); } },
|
||||
/*{ "APRS", ui::Color::dark_grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "DMR", ui::Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
@ -502,6 +531,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
//{ "About", ui::Color::cyan(), nullptr, [&nav](){ nav.push<AboutView>(); } }
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
set_arrow_enabled(false);
|
||||
//set_highlighted(1); // Startup selection
|
||||
}
|
||||
|
||||
@ -522,6 +552,7 @@ SystemView::SystemView(
|
||||
set_style(&style_default);
|
||||
|
||||
constexpr ui::Dim status_view_height = 16;
|
||||
constexpr ui::Dim info_view_height = 16;
|
||||
|
||||
add_child(&status_view);
|
||||
status_view.set_parent_rect({
|
||||
@ -537,13 +568,29 @@ SystemView::SystemView(
|
||||
{ 0, status_view_height },
|
||||
{ parent_rect.width(), static_cast<ui::Dim>(parent_rect.height() - status_view_height) }
|
||||
});
|
||||
|
||||
add_child(&info_view);
|
||||
info_view.set_parent_rect({
|
||||
{0, 19 * 16},
|
||||
{ parent_rect.width(), info_view_height }
|
||||
});
|
||||
|
||||
navigation_view.on_view_changed = [this](const View& new_view) {
|
||||
|
||||
if(!this->navigation_view.is_top()){
|
||||
remove_child(&info_view);
|
||||
}
|
||||
else{
|
||||
add_child(&info_view);
|
||||
}
|
||||
|
||||
this->status_view.set_back_enabled(!this->navigation_view.is_top());
|
||||
this->status_view.set_title_image_enabled(this->navigation_view.is_top());
|
||||
this->status_view.set_dirty();
|
||||
this->status_view.set_title(new_view.title());
|
||||
this->status_view.set_dirty();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
// portapack::persistent_memory::set_playdead_sequence(0x8D1);
|
||||
|
||||
@ -557,6 +604,9 @@ SystemView::SystemView(
|
||||
|
||||
if (portapack::persistent_memory::config_splash())
|
||||
navigation_view.push<BMPView>();
|
||||
status_view.set_back_enabled(false);
|
||||
status_view.set_title_image_enabled(true);
|
||||
status_view.set_dirty();
|
||||
//else
|
||||
// navigation_view.push<SystemMenuView>();
|
||||
|
||||
|
@ -110,7 +110,7 @@ public:
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
private:
|
||||
static constexpr auto default_title = " v1.1.1"; // TODO: Move the version somewhere
|
||||
static constexpr auto default_title = "";
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@ -208,6 +208,29 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class InformationView : public View {
|
||||
public:
|
||||
InformationView(NavigationView& nav);
|
||||
|
||||
private:
|
||||
static constexpr auto version_string = "v1.2";
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop {
|
||||
{ 0, 0 * 16, 240, 16 },
|
||||
{33, 33, 33}
|
||||
};
|
||||
|
||||
Text version {
|
||||
{2, 0, 11 * 8, 16},
|
||||
version_string
|
||||
};
|
||||
|
||||
LiveDateTime ltime {
|
||||
{174, 0, 8 * 8, 16}
|
||||
};
|
||||
};
|
||||
|
||||
class BMPView : public View {
|
||||
public:
|
||||
BMPView(NavigationView& nav);
|
||||
@ -262,6 +285,7 @@ public:
|
||||
|
||||
private:
|
||||
SystemStatusView status_view { navigation_view };
|
||||
InformationView info_view { navigation_view };
|
||||
NavigationView navigation_view { };
|
||||
Context& context_;
|
||||
};
|
||||
|
@ -140,7 +140,8 @@ private:
|
||||
}
|
||||
};
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> packet_builder_fsk_4800_Vaisala {
|
||||
{ 0b00001000011011010101001110001000, 32, 1 },
|
||||
{ 0b00001000011011010101001110001000, 32, 1 }, //euquiq Header detects 4 of 8 bytes 0x10B6CA11 /this is in raw format) (these bits are not passed at the beginning of packet)
|
||||
//{ 0b0000100001101101010100111000100001000100011010010100100000011111, 64, 1 }, //euquiq whole header detection would be 8 bytes.
|
||||
{ },
|
||||
{ 320 * 8 },
|
||||
[this](const baseband::Packet& packet) {
|
||||
|
@ -300,8 +300,8 @@ void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint
|
||||
|
||||
v_rate_coded = (v_rate / 64) + 1;
|
||||
|
||||
velo_ew_abs = abs(velo_ew);
|
||||
velo_ns_abs = abs(velo_ns);
|
||||
velo_ew_abs = abs(velo_ew) + 1;
|
||||
velo_ns_abs = abs(velo_ns) + 1;
|
||||
v_rate_coded_abs = abs(v_rate_coded);
|
||||
|
||||
make_frame_adsb(frame, ICAO_address);
|
||||
@ -317,4 +317,52 @@ void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint
|
||||
frame.make_CRC();
|
||||
}
|
||||
|
||||
// Decoding method from dump1090
|
||||
adsb_vel decode_frame_velo(ADSBFrame& frame){
|
||||
adsb_vel velo {false, 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;
|
||||
|
||||
if((frame_data[8] & 0x8) >> 3) 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(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 = sqrt(velo_ns*velo_ns + velo_ew*velo_ew);
|
||||
|
||||
if(velo.speed){
|
||||
//calculate heading in degrees from ew/ns velocities
|
||||
int16_t heading_temp = (int16_t)(atan2(velo_ew,velo_ns) * 180.0 / pi);
|
||||
// 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;
|
||||
}
|
||||
|
||||
return velo;
|
||||
|
||||
}
|
||||
|
||||
} /* namespace adsb */
|
||||
|
@ -56,6 +56,13 @@ struct adsb_pos {
|
||||
int32_t altitude;
|
||||
};
|
||||
|
||||
struct adsb_vel {
|
||||
bool valid;
|
||||
int32_t speed; //knot
|
||||
uint16_t heading; //degree
|
||||
int32_t v_rate; //ft/min
|
||||
};
|
||||
|
||||
const float CPR_MAX_VALUE = 131072.0;
|
||||
|
||||
const float adsb_lat_lut[58] = {
|
||||
@ -89,6 +96,8 @@ adsb_pos decode_frame_pos(ADSBFrame& frame_even, ADSBFrame& frame_odd);
|
||||
void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed,
|
||||
const float angle, const int32_t v_rate);
|
||||
|
||||
adsb_vel decode_frame_velo(ADSBFrame& frame);
|
||||
|
||||
//void encode_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code);
|
||||
|
||||
void encode_frame_squawk(ADSBFrame& frame, const uint32_t squawk);
|
||||
|
@ -41,6 +41,10 @@ public:
|
||||
return (raw_data[4] >> 3);
|
||||
}
|
||||
|
||||
uint8_t get_msg_sub() {
|
||||
return (raw_data[4] & 7);
|
||||
}
|
||||
|
||||
uint32_t get_ICAO_address() {
|
||||
return (raw_data[1] << 16) + (raw_data[2] << 8) + raw_data[3];
|
||||
}
|
||||
|
@ -22,9 +22,25 @@
|
||||
|
||||
#include "sonde_packet.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include <cstring>
|
||||
//#include <complex>
|
||||
|
||||
namespace sonde {
|
||||
|
||||
//Defines for Vaisala RS41, from https://github.com/rs1729/RS/blob/master/rs41/rs41sg.c
|
||||
#define MASK_LEN 64
|
||||
#define pos_FrameNb 0x37 //0x03B // 2 byte
|
||||
#define pos_SondeID 0x39 //0x03D // 8 byte
|
||||
#define pos_Voltage 0x041 //0x045 // 3 bytes (but first one is the important one) voltage x 10 ie: 26 = 2.6v
|
||||
#define pos_CalData 0x04E //0x052 // 1 byte, counter 0x00..0x32
|
||||
#define pos_GPSweek 0x091 //0x095 // 2 byte
|
||||
#define pos_GPSTOW 0x093 //0x097 // 4 byte
|
||||
#define pos_GPSecefX 0x110 //0x114 // 4 byte
|
||||
#define pos_GPSecefY 0x114 //0x118 // 4 byte (not actually used since Y and Z are following X, and grabbed in that same loop)
|
||||
#define pos_GPSecefZ 0x118 //0x11C // 4 byte (same as Y)
|
||||
|
||||
#define PI 3.1415926535897932384626433832795 //3.1416 //(3.1415926535897932384626433832795)
|
||||
|
||||
Packet::Packet(
|
||||
const baseband::Packet& packet,
|
||||
const Type type
|
||||
@ -60,37 +76,65 @@ Packet::Type Packet::type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
/*uint8_t Packet::vaisala_descramble(const uint32_t pos) {
|
||||
return reader_raw.read(pos * 8, 8) ^ vaisala_mask[pos & 63];
|
||||
};*/
|
||||
//euquiq here:
|
||||
//RS41SG 320 bits header, 320bytes frame (or more if it is an "extended frame")
|
||||
//The raw data is xor-scrambled with the values in the 64 bytes vaisala_mask (see.hpp)
|
||||
|
||||
uint32_t Packet::GPS_altitude() const {
|
||||
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2))
|
||||
return (reader_bi_m.read(22 * 8, 32) / 1000) - 48;
|
||||
else if (type_ == Type::Vaisala_RS41_SG) {
|
||||
/*uint32_t altitude_ecef = 0;
|
||||
for (uint32_t i = 0; i < 4; i++)
|
||||
altitude_ecef = (altitude_ecef << 8) + vaisala_descramble(0x11C + i);*/
|
||||
// TODO: and a bunch of maths (see ecef2elli() from RS1729)
|
||||
return 0;
|
||||
} else
|
||||
return 0; // Unknown
|
||||
}
|
||||
|
||||
float Packet::GPS_latitude() const {
|
||||
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2))
|
||||
return reader_bi_m.read(14 * 8, 32) / ((1ULL << 32) / 360.0);
|
||||
//else if (type_ == Type::Vaisala_RS41_SG)
|
||||
// return vaisala_descramble();
|
||||
else
|
||||
return 0; // Unknown
|
||||
}
|
||||
uint8_t Packet::vaisala_descramble(const uint32_t pos) const {
|
||||
//return reader_raw.read(pos * 8, 8) ^ vaisala_mask[pos & 63];
|
||||
// packet_[i]; its a bit; packet_.size the total (should be 2560 bits)
|
||||
uint8_t value = 0;
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
value = (value << 1) | packet_[(pos * 8) + (7 -i)]; //get the byte from the bits collection
|
||||
|
||||
float Packet::GPS_longitude() const {
|
||||
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2))
|
||||
return reader_bi_m.read(18 * 8, 32) / ((1ULL << 32) / 360.0);
|
||||
else
|
||||
return 0; // Unknown
|
||||
//packetReader reader { packet_ }; //This works just as above.
|
||||
//value = reader.read(pos * 8,8);
|
||||
//shift pos because first 4 bytes are consumed by proc_sonde in finding the vaisala signature
|
||||
uint32_t mask_pos = pos + 4;
|
||||
value = value ^ vaisala_mask[mask_pos % MASK_LEN]; //descramble with the xor pseudorandom table
|
||||
return value;
|
||||
};
|
||||
|
||||
GPS_data Packet::get_GPS_data() const {
|
||||
GPS_data result;
|
||||
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2)) {
|
||||
|
||||
result.alt = (reader_bi_m.read(22 * 8, 32) / 1000) - 48;
|
||||
result.lat = reader_bi_m.read(14 * 8, 32) / ((1ULL << 32) / 360.0);
|
||||
result.lon = reader_bi_m.read(18 * 8, 32) / ((1ULL << 32) / 360.0);
|
||||
|
||||
} else if (type_ == Type::Vaisala_RS41_SG) {
|
||||
|
||||
uint8_t XYZ_bytes[4];
|
||||
int32_t XYZ; // 32bit
|
||||
double_t X[3];
|
||||
for (int32_t k = 0; k < 3; k++) { //Get X,Y,Z ECEF position from GPS
|
||||
for (int32_t i = 0; i < 4; i++) //each one is 4 bytes (32 bits)
|
||||
XYZ_bytes[i] = vaisala_descramble(pos_GPSecefX + (4*k) + i);
|
||||
memcpy(&XYZ, XYZ_bytes, 4);
|
||||
X[k] = XYZ / 100.0;
|
||||
}
|
||||
|
||||
double_t a = 6378137.0;
|
||||
double_t b = 6356752.31424518;
|
||||
double_t e = sqrt( (a*a - b*b) / (a*a) );
|
||||
double_t ee = sqrt( (a*a - b*b) / (b*b) );
|
||||
|
||||
double_t lam = atan2( X[1] , X[0] );
|
||||
double_t p = sqrt( X[0]*X[0] + X[1]*X[1] );
|
||||
double_t t = atan2( X[2]*a , p*b );
|
||||
double_t phi = atan2( X[2] + ee*ee * b * sin(t)*sin(t)*sin(t) ,
|
||||
p - e*e * a * cos(t)*cos(t)*cos(t) );
|
||||
|
||||
double_t R = a / sqrt( 1 - e*e*sin(phi)*sin(phi) );
|
||||
|
||||
result.alt = p / cos(phi) - R;
|
||||
result.lat = phi*180/PI;
|
||||
result.lon = lam*180/PI;
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Packet::battery_voltage() const {
|
||||
@ -98,8 +142,13 @@ uint32_t Packet::battery_voltage() const {
|
||||
return (reader_bi_m.read(69 * 8, 8) + (reader_bi_m.read(70 * 8, 8) << 8)) * 1000 / 150;
|
||||
else if (type_ == Type::Meteomodem_M2K2)
|
||||
return reader_bi_m.read(69 * 8, 8) * 66; // Actually 65.8
|
||||
else
|
||||
else if (type_ == Type::Vaisala_RS41_SG) {
|
||||
uint32_t voltage = vaisala_descramble(pos_Voltage) * 100; //byte 69 = voltage * 10 (check if this value needs to be multiplied)
|
||||
return voltage;
|
||||
}
|
||||
else {
|
||||
return 0; // Unknown
|
||||
}
|
||||
}
|
||||
|
||||
std::string Packet::type_string() const {
|
||||
@ -127,12 +176,33 @@ std::string Packet::serial_number() const {
|
||||
to_string_dec_uint(reader_bi_m.read(93 * 8 + 24, 3), 1) +
|
||||
to_string_dec_uint(reader_bi_m.read(93 * 8 + 27, 13), 4, '0');
|
||||
|
||||
} else
|
||||
} else if(type() == Type::Vaisala_RS41_SG) {
|
||||
std::string serial_id = "";
|
||||
uint8_t achar;
|
||||
for (uint8_t i=0; i<8; i++) { //euquiq: Serial ID is 8 bytes long, each byte a char
|
||||
achar = vaisala_descramble(pos_SondeID + i);
|
||||
if (achar < 32 || achar > 126) return "?"; //Maybe there are ids with less than 8 bytes and this is not OK.
|
||||
serial_id += (char)achar;
|
||||
}
|
||||
return serial_id;
|
||||
} else
|
||||
return "?";
|
||||
}
|
||||
|
||||
FormattedSymbols Packet::symbols_formatted() const {
|
||||
return format_symbols(decoder_);
|
||||
if (type() == Type::Vaisala_RS41_SG) { //Euquiq: now we distinguish different types
|
||||
uint32_t bytes = packet_.size() / 8; //Need the byte amount, which if full, it SHOULD be 320 size() should return 2560
|
||||
std::string hex_data;
|
||||
std::string hex_error;
|
||||
hex_data.reserve(bytes * 2); //2 hexa chars per byte
|
||||
hex_error.reserve(1);
|
||||
for (uint32_t i=0; i < bytes; i++) //log will show the packet starting on the last 4 bytes from signature 93DF1A60
|
||||
hex_data += to_string_hex(vaisala_descramble(i),2);
|
||||
return { hex_data, hex_error };
|
||||
|
||||
} else {
|
||||
return format_symbols(decoder_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Packet::crc_ok() const {
|
||||
|
@ -32,6 +32,12 @@
|
||||
|
||||
namespace sonde {
|
||||
|
||||
struct GPS_data {
|
||||
uint32_t alt { 0 };
|
||||
float lat { 0 };
|
||||
float lon { 0 };
|
||||
};
|
||||
|
||||
class Packet {
|
||||
public:
|
||||
enum class Type : uint32_t {
|
||||
@ -41,7 +47,7 @@ public:
|
||||
Meteomodem_M2K2 = 3,
|
||||
Vaisala_RS41_SG = 4,
|
||||
};
|
||||
|
||||
|
||||
Packet(const baseband::Packet& packet, const Type type);
|
||||
|
||||
size_t length() const;
|
||||
@ -56,9 +62,7 @@ public:
|
||||
std::string serial_number() const;
|
||||
uint32_t battery_voltage() const;
|
||||
|
||||
uint32_t GPS_altitude() const;
|
||||
float GPS_latitude() const;
|
||||
float GPS_longitude() const;
|
||||
GPS_data get_GPS_data() const;
|
||||
|
||||
FormattedSymbols symbols_formatted() const;
|
||||
|
||||
@ -75,17 +79,20 @@ private:
|
||||
0xD0, 0xBC, 0xB4, 0xB6, 0x06, 0xAA, 0xF4, 0x23,
|
||||
0x78, 0x6E, 0x3B, 0xAE, 0xBF, 0x7B, 0x4C, 0xC1
|
||||
};
|
||||
|
||||
GPS_data ecef_to_gps() const;
|
||||
|
||||
//uint8_t vaisala_descramble(const uint32_t pos);
|
||||
uint8_t vaisala_descramble(uint32_t pos) const;
|
||||
|
||||
const baseband::Packet packet_;
|
||||
const BiphaseMDecoder decoder_;
|
||||
const FieldReader<BiphaseMDecoder, BitRemapNone> reader_bi_m;
|
||||
Type type_;
|
||||
|
||||
using packetReader = FieldReader<baseband::Packet, BitRemapByteReverse>; //baseband::Packet instead of BiphaseMDecoder
|
||||
bool crc_ok_M10() const;
|
||||
};
|
||||
|
||||
} /* namespace sonde */
|
||||
|
||||
#endif/*__SONDE_PACKET_H__*/
|
||||
#endif/*__SONDE_PACKET_H__*/
|
@ -409,10 +409,26 @@ void Labels::paint(Painter& painter) {
|
||||
|
||||
void LiveDateTime::on_tick_second() {
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
text = "";
|
||||
|
||||
text = to_string_dec_uint(datetime.month(), 2, '0') + "/" + to_string_dec_uint(datetime.day(), 2, '0') + " " +
|
||||
to_string_dec_uint(datetime.hour(), 2, '0') + ":" + to_string_dec_uint(datetime.minute(), 2, '0');
|
||||
if(date_enabled){
|
||||
text = to_string_dec_uint(datetime.month(), 2, '0') + "/" + to_string_dec_uint(datetime.day(), 2, '0') + " ";
|
||||
}
|
||||
|
||||
text = text + to_string_dec_uint(datetime.hour(), 2, '0') + ":" + to_string_dec_uint(datetime.minute(), 2, '0');
|
||||
|
||||
if(seconds_enabled){
|
||||
text += ":";
|
||||
|
||||
if(init_delay==0)
|
||||
text += to_string_dec_uint(datetime.second(), 2, '0');
|
||||
else
|
||||
{
|
||||
// Placeholder while the seconds are not updated
|
||||
text += "XX";
|
||||
init_delay--;
|
||||
}
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@ -444,6 +460,14 @@ void LiveDateTime::paint(Painter& painter) {
|
||||
);
|
||||
}
|
||||
|
||||
void LiveDateTime::set_date_enabled(bool new_value){
|
||||
this->date_enabled = new_value;
|
||||
}
|
||||
|
||||
void LiveDateTime::set_seconds_enabled(bool new_value) {
|
||||
this->seconds_enabled = new_value;
|
||||
}
|
||||
|
||||
/* BigFrequency **********************************************************/
|
||||
|
||||
BigFrequency::BigFrequency(
|
||||
@ -1760,13 +1784,13 @@ void VuMeter::paint(Painter& painter) {
|
||||
lit = true;
|
||||
|
||||
if (bar == 0)
|
||||
color = lit ? Color::red() : Color::dark_red();
|
||||
color = lit ? Color::red() : Color::dark_grey();
|
||||
else if (bar == 1)
|
||||
color = lit ? Color::orange() : Color::dark_orange();
|
||||
color = lit ? Color::orange() : Color::dark_grey();
|
||||
else if ((bar == 2) || (bar == 3))
|
||||
color = lit ? Color::yellow() : Color::dark_yellow();
|
||||
color = lit ? Color::yellow() : Color::dark_grey();
|
||||
else
|
||||
color = lit ? Color::green() : Color::dark_green();
|
||||
color = lit ? Color::green() : Color::dark_grey();
|
||||
|
||||
painter.fill_rectangle({ pos.x(), pos.y() + (Coord)(bar * (LED_height + 1)), width, (Coord)LED_height }, color);
|
||||
}
|
||||
|
@ -243,7 +243,10 @@ public:
|
||||
~LiveDateTime();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
|
||||
void set_seconds_enabled(bool new_value);
|
||||
void set_date_enabled(bool new_value);
|
||||
|
||||
std::string& string() {
|
||||
return text;
|
||||
}
|
||||
@ -251,6 +254,10 @@ public:
|
||||
private:
|
||||
void on_tick_second();
|
||||
|
||||
uint16_t init_delay = 4;
|
||||
bool date_enabled = true;
|
||||
bool seconds_enabled = false;
|
||||
|
||||
rtc::RTC datetime { };
|
||||
SignalToken signal_token_tick_second { };
|
||||
std::string text { };
|
||||
|
Loading…
Reference in New Issue
Block a user