ADSB RX now works \o/

Added tabs in RDS TX, multiple groups can be sent at once
Bugfix: text not updating on UI after text prompt
This commit is contained in:
furrtek 2017-08-16 10:02:57 +01:00
parent 160359166a
commit 9d902bc224
18 changed files with 664 additions and 590 deletions

View File

@ -25,6 +25,7 @@
#include "portapack_shared_memory.hpp"
// RDS infos:
// One frame = X groups (as necessary)
// One group = 4 blocks = 4 * 26 bits
// One block = 26 bits (16 bits data + 10 bits checkword)
// Sent MSB first
@ -44,11 +45,6 @@ uint32_t make_block(uint32_t data, uint16_t offset) {
return (data << 10) | (CRC ^ offset);
}
// Todo:
// Make PI
// TA/TP flags
// Group selection
// Boolean to binary
uint8_t b2b(const bool in) {
if (in)
@ -58,27 +54,36 @@ uint8_t b2b(const bool in) {
}
// Type 0B groups are like 0A groups but without alternative frequency data
void make_0B_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA,
RDSGroup make_0B_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA,
const bool MS, const bool DI, const uint8_t C, const std::string chars) {
group->block[0] = PI_code;
group->block[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3);
group->block[2] = PI_code;
group->block[3] = (chars[0] << 8) | chars[1];
RDSGroup group;
group.block[0] = PI_code;
group.block[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3);
group.block[2] = PI_code;
group.block[3] = (chars[0] << 8) | chars[1];
return group;
}
// For RadioText, up to 64 chars with 2A, 32 chars with 2B
void make_2A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB,
RDSGroup make_2A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB,
const uint8_t segment, const std::string chars) {
group->block[0] = PI_code;
group->block[1] = (0x2 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(AB) << 4) | (segment & 15);
group->block[2] = (chars[0] << 8) | chars[1];
group->block[3] = (chars[2] << 8) | chars[3];
RDSGroup group;
group.block[0] = PI_code;
group.block[1] = (0x2 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(AB) << 4) | (segment & 15);
group.block[2] = (chars[0] << 8) | chars[1];
group.block[3] = (chars[2] << 8) | chars[3];
return group;
}
// Time and date - usually one message per minute - Month: 1~12 - Day: 1~31 - Hour/Minute: 0~59 - Local offset: -12/+12 from UTC
void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY,
RDSGroup make_4A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY,
const uint16_t year, const uint8_t month, const uint8_t day,
const uint8_t hour, const uint8_t minute, const int8_t local_offset) {
RDSGroup group;
uint32_t L = 0;
uint32_t day_code;
@ -86,80 +91,64 @@ void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, con
day_code = 14956 + day + (uint32_t)((float)(year - 1900 - L) * 365.25) + uint16_t((float)((month + 1) + L * 12) * 30.6001);
group->block[0] = PI_code;
group->block[1] = (0x4 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | ((day_code & 0x18000) >> 15);
group->block[2] = ((day_code & 0x7FFF) << 1) | (hour >> 4);
group->block[3] = ((hour & 15) << 12) | ((minute & 0x3F) << 6) | (local_offset & 0x3F);
group.block[0] = PI_code;
group.block[1] = (0x4 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | ((day_code & 0x18000) >> 15);
group.block[2] = ((day_code & 0x7FFF) << 1) | (hour >> 4);
group.block[3] = ((hour & 15) << 12) | ((minute & 0x3F) << 6) | (local_offset & 0x3F);
return group;
}
uint16_t gen_PSN(const std::string & psname, const RDS_flags * rds_flags) {
void gen_PSN(std::vector<RDSGroup>& frame, const std::string& psname, const RDS_flags * rds_flags) {
uint8_t c;
RDS_group groups[4] = { 0 };
RDSGroup group;
frame.clear();
// 4 groups with 2 PSN characters in each
for (c = 0; c < 4; c++)
make_0B_group(&groups[c], rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, rds_flags->TA, rds_flags->MS, rds_flags->DI, c, psname.substr(c * 2, 2));
// Generate checkbits for each block of each group
for (c = 0; c < 4; c++) {
groups[c].block[0] = make_block(groups[c].block[0], RDS_OFFSET_A);
groups[c].block[1] = make_block(groups[c].block[1], RDS_OFFSET_B);
groups[c].block[2] = make_block(groups[c].block[2], RDS_OFFSET_Cp); // C' !
groups[c].block[3] = make_block(groups[c].block[3], RDS_OFFSET_D);
group = make_0B_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, rds_flags->TA, rds_flags->MS, rds_flags->DI, c, psname.substr(c * 2, 2));
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
group.block[1] = make_block(group.block[1], RDS_OFFSET_B);
group.block[2] = make_block(group.block[2], RDS_OFFSET_Cp); // C' !
group.block[3] = make_block(group.block[3], RDS_OFFSET_D);
frame.emplace_back(group);
}
uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
// Copy to tx_data for baseband
for (c = 0; c < 4 * 4; c++)
tx_data_u32[c] = groups[c >> 2].block[c & 3];
return 4 * 4 * 26;
}
uint16_t gen_RadioText(const std::string & text, const bool AB, const RDS_flags * rds_flags) {
void gen_RadioText(std::vector<RDSGroup>& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags) {
size_t c;
RDS_group * groups_ptr;
//RDSGroup * groups_ptr;
std::string radiotext_buffer = text;
size_t rt_length, groups;
size_t rt_length, group_count;
RDSGroup group;
radiotext_buffer += 0x0D;
rt_length = radiotext_buffer.length();
rt_length = (rt_length + 3) & 0xFC;
groups = rt_length >> 2; // 4 characters per group
group_count = rt_length >> 2; // 4 characters per group
groups_ptr = (RDS_group*)chHeapAlloc(0, groups * sizeof(RDS_group));
//groups_ptr = (RDSGroup*)chHeapAlloc(0, group_count * sizeof(RDSGroup));
for (c = 0; c < groups; c++)
make_2A_group(&groups_ptr[c], rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, AB, c, radiotext_buffer.substr(c * 4, 4));
// Generate checkbits for each block of each group
for (c = 0; c < groups; c++) {
groups_ptr[c].block[0] = make_block(groups_ptr[c].block[0], RDS_OFFSET_A);
groups_ptr[c].block[1] = make_block(groups_ptr[c].block[1], RDS_OFFSET_B);
groups_ptr[c].block[2] = make_block(groups_ptr[c].block[2], RDS_OFFSET_C);
groups_ptr[c].block[3] = make_block(groups_ptr[c].block[3], RDS_OFFSET_D);
frame.clear();
for (c = 0; c < group_count; c++) {
group = make_2A_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, AB, c, radiotext_buffer.substr(c * 4, 4));
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
group.block[1] = make_block(group.block[1], RDS_OFFSET_B);
group.block[2] = make_block(group.block[2], RDS_OFFSET_C);
group.block[3] = make_block(group.block[3], RDS_OFFSET_D);
frame.emplace_back(group);
}
uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
// Copy to tx_data for baseband
for (c = 0; c < groups * 4; c++)
tx_data_u32[c] = groups_ptr[c >> 2].block[c & 3];
chHeapFree(groups_ptr);
return groups * 4 * 26;
}
uint16_t gen_ClockTime(const RDS_flags * rds_flags,
void gen_ClockTime(std::vector<RDSGroup>& frame, const RDS_flags * rds_flags,
const uint16_t year, const uint8_t month, const uint8_t day,
const uint8_t hour, const uint8_t minute, const int8_t local_offset) {
uint8_t c;
RDS_group group = { 0 };
RDSGroup group;
make_4A_group(&group, rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, year, month, day, hour, minute, local_offset);
group = make_4A_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, year, month, day, hour, minute, local_offset);
// Generate checkbits for each block
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
@ -167,13 +156,8 @@ uint16_t gen_ClockTime(const RDS_flags * rds_flags,
group.block[2] = make_block(group.block[2], RDS_OFFSET_C);
group.block[3] = make_block(group.block[3], RDS_OFFSET_D);
uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
// Copy to tx_data for baseband
for (c = 0; c < 4; c++)
tx_data_u32[c] = group.block[c];
return 4 * 26;
frame.clear();
frame.emplace_back(group);
}
} /* namespace rds */

View File

@ -20,8 +20,8 @@
* Boston, MA 02110-1301, USA.
*/
#include <cstring>
#include <string>
#include <vector>
#include "ch.h"
#ifndef __RDS_H__
@ -44,24 +44,24 @@ struct RDS_flags {
bool MS;
};
struct RDS_group {
struct RDSGroup {
uint32_t block[4];
};
uint32_t make_block(uint32_t blockdata, uint16_t offset);
uint8_t b2b(const bool in);
void make_0B_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA,
RDSGroup make_0B_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA,
const bool MS, const bool DI, const uint8_t C, const std::string chars);
void make_2A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB,
RDSGroup make_2A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB,
const uint8_t segment, const std::string chars);
void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY,
RDSGroup make_4A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY,
const uint16_t year, const uint8_t month, const uint8_t day,
const uint8_t hour, const uint8_t minute, const int8_t local_offset);
uint16_t gen_PSN(const std::string & psname, const RDS_flags * rds_flags);
uint16_t gen_RadioText(const std::string & text, const bool AB, const RDS_flags * rds_flags);
uint16_t gen_ClockTime(const RDS_flags * rds_flags,
void gen_PSN(std::vector<RDSGroup>& frame, const std::string& psname, const RDS_flags * rds_flags);
void gen_RadioText(std::vector<RDSGroup>& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags);
void gen_ClockTime(std::vector<RDSGroup>& frame, const RDS_flags * rds_flags,
const uint16_t year, const uint8_t month, const uint8_t day,
const uint8_t hour, const uint8_t minute, const int8_t local_offset);

View File

@ -45,12 +45,16 @@ void RecentEntriesTable<ADSBRecentEntries>::draw(
Painter& painter,
const Style& style
) {
painter.draw_string(target_rect.location(), style, to_string_hex_array((uint8_t*)entry.raw_data, 10) + " " + entry.time);
painter.draw_string(
target_rect.location(),
style,
to_string_hex(entry.ICAO_address, 6) + " " + entry.callsign + " " + (entry.hits <= 9999 ? to_string_dec_uint(entry.hits, 5) : "9999+") + " " + entry.time
//to_string_hex_array((uint8_t*)entry.raw_data, 10) + " " + entry.time
);
}
void ADSBRxView::focus() {
offset_field.focus();
offset_field.set_value(13179);
field_lna.focus();
}
ADSBRxView::~ADSBRxView() {
@ -58,205 +62,50 @@ ADSBRxView::~ADSBRxView() {
baseband::shutdown();
}
/*bool ADSBRxView::analyze(uint64_t offset) {
Coord lcd_x = 0, lcd_y = 0;
int8_t re, im;
adsb_frame frame;
int16_t file_data[128]; // 256 bytes / 2 IQ / 16 bits = 64 samples
complex8_t iq_data[256]; // 256 samples
uint64_t file_offset = 0;
uint8_t data_put = 0, data_get = 0;
int16_t f_re, f_im;
uint32_t c;
uint8_t level, bit, byte;
Color mark_color;
size_t preamble_count = 0, null_count = 0, bit_count = 0, sample_count = 0;
bool decoding = false;
float prev_mag = 0, mag;
float threshold, threshold_low, threshold_high;
std::string bits;
std::string hex_str;
bool confidence, first_in_window, last_in_window;
std::pair<float, uint8_t> shifter[ADSB_PREAMBLE_LENGTH];
iq_file.seek(offset * 2048); // 256
for (;;) {
if (data_put == data_get) {
auto result = iq_file.read(file_data, 256);
if (!result.is_error()) {
// Convert file's C16 to C8
for (c = 0; c < (result.value() / 4); c++) {
f_re = file_data[(c * 2) + 0] >> 5; // >> 8 (<< 3 amp.)
f_im = file_data[(c * 2) + 1] >> 5;
iq_data[data_put] = { (int8_t)f_re, (int8_t)f_im };
data_put++;
}
file_offset += result.value();
if (file_offset >= 2048) {
//text_debug_e.set("Read @ " + to_string_dec_uint(offset * 256 / 2000 / 4) + "ms ");
break;
}
} else {
text_debug_a.set("Read error");
return false;
}
}
re = iq_data[data_get].real();
im = iq_data[data_get].imag();
mag = __builtin_sqrtf((re * re) + (im * im)) * k;
data_get++;
// Only used for preamble detection and visualisation
level = (mag < 0.3) ? 0 : // Blank weak signals
(mag > prev_mag) ? 1 : 0;
if (decoding) {
// Decode
mark_color = Color::grey();
// 1 bit lasts 2 samples
if (sample_count & 1) {
if ((prev_mag < threshold_low) && (mag < threshold_low)) {
// Both under window, silence.
mark_color = Color::black();
if (null_count > 3) {
text_debug_b.set("Bits:" + bits.substr(0, 25));
text_debug_c.set("Hex:" + hex_str.substr(0, 26));
text_debug_d.set("DF=" + to_string_dec_uint(frame.get_DF()) + " ICAO=" + to_string_hex(frame.get_ICAO_address(), 6));
if ((frame.get_DF() == 17) && (frame.get_msg_type() >= 1) && (frame.get_msg_type() <= 4)) {
text_debug_a.set("Callsign:" + frame.get_callsign());
return true;
} else {
text_debug_a.set("No ID data");
return false;
}
decoding = false;
} else
null_count++;
confidence = false;
if (prev_mag > mag)
bit = 1;
else
bit = 0;
mark_color = bit ? Color::dark_red() : Color::dark_green();
} else {
null_count = 0;
first_in_window = ((prev_mag >= threshold_low) && (prev_mag <= threshold_high));
last_in_window = ((mag >= threshold_low) && (mag <= threshold_high));
if ((first_in_window && !last_in_window) || (!first_in_window && last_in_window)) {
confidence = true;
if (prev_mag > mag)
bit = 1;
else
bit = 0;
} else {
confidence = false;
if (prev_mag > mag)
bit = 1;
else
bit = 0;
}
mark_color = bit ? Color::red() : Color::green();
}
bits.append(bit ? "1" : "0"); // DEBUG
byte = bit | (byte << 1);
bit_count++;
if (!(bit_count & 7)) {
// Got one byte
hex_str += to_string_hex(byte, 2); // DEBUG
frame.push_byte(byte);
}
}
sample_count++;
} else {
// Look for preamble
mark_color = Color::white();
// Shift
for (c = 0; c < (ADSB_PREAMBLE_LENGTH - 1); c++)
shifter[c] = shifter[c + 1];
shifter[15] = std::make_pair(mag, level);
// Compare
for (c = 0; c < ADSB_PREAMBLE_LENGTH; c++) {
if (shifter[c].second != adsb_preamble[c])
break;
}
if (c == ADSB_PREAMBLE_LENGTH) {
preamble_count++;
if (preamble_count == 1) {
// Try decoding the first frame found
decoding = true;
sample_count = 0;
// Compute preamble pulses power to set thresholds
threshold = (shifter[0].first + shifter[2].first + shifter[7].first + shifter[9].first) / 4;
threshold_high = threshold * 1.414; // +3dB
threshold_low = threshold * 0.707; // -3dB
}
}
}
prev_mag = mag;
if (preamble_count) {
if (lcd_y < 188) {
mag *= 16;
// Background
display.fill_rectangle({lcd_x, 100 + lcd_y, 2, 32 - (int)mag}, decoding ? mark_color : Color::grey());
// Bar
display.fill_rectangle({lcd_x, 132 + lcd_y - (int)mag, 2, (int)mag}, Color::white());
// Level
display.fill_rectangle({lcd_x, 132 + lcd_y, 2, 4}, decoding ? ((sample_count & 1) ? Color::white() : Color::light_grey()) : (level ? Color::white() : Color::dark_blue()));
if (lcd_x == 238) {
lcd_x = 0;
lcd_y += 40;
} else {
lcd_x += 2;
}
}
}
}
return false;
}*/
void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
rtc::RTC datetime;
std::string str_timestamp;
auto frame = message->frame;
auto& entry = ::on_packet(recent, frame.get_ICAO_address());
std::string callsign;
rtcGetTime(&RTCD1, &datetime);
str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
to_string_dec_uint(datetime.second(), 2, '0');
entry.set_time(str_timestamp);
entry.set_raw(frame.get_raw_data());
recent_entries_view.set_dirty();
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
if (frame.check_CRC() && frame.get_ICAO_address()) {
auto& entry = ::on_packet(recent, ICAO_address);
rtcGetTime(&RTCD1, &datetime);
str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
to_string_dec_uint(datetime.second(), 2, '0');
entry.set_time(str_timestamp);
entry.set_raw(frame.get_raw_data());
entry.inc_hit();
if (frame.get_DF() == DF_ADSB) {
if (frame.get_msg_type() == TC_IDENT) {
callsign = decode_frame_id(frame);
entry.set_callsign(callsign);
} else if (frame.get_msg_type() == TC_AIRBORNE_POS) {
callsign = "Altitude: " + to_string_dec_uint(decode_frame_pos(frame)) + "ft";
entry.set_pos(callsign);
}
}
recent_entries_view.set_dirty();
}
}
ADSBRxView::ADSBRxView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
add_children({
//&labels,
&offset_field,
//&button_ffw,
&labels,
&rssi,
&field_lna,
&field_vga,
&text_debug_a,
&text_debug_b,
&text_debug_c,
@ -264,29 +113,16 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
});
recent_entries_view.set_parent_rect({ 0, 64, 240, 224 });
// File must be 16bit complex @ 2Msps !
/*auto result = iq_file.open("ADSB.C16");
if (result.is_valid()) {
text_debug_a.set("Can't open file");
}*/
/*offset_field.on_change = [this, &nav](int32_t value) {
// TODO
};*/
/*button_ffw.on_select = [this, &nav](Button&) {
//nav.push<GeoMapView>(GeoMapView::Mode::SHOW);
while (!analyze(f_offset)) {
f_offset++;
}
offset_field.set_value(f_offset);
f_offset++;
};*/
recent_entries_view.on_select = [this](const ADSBRecentEntry& entry) {
text_debug_a.set(entry.geo_pos);
};
baseband::set_adsb();
receiver_model.set_tuning_frequency(1090000000);
receiver_model.set_rf_amp(true);
field_lna.set_value(40);
field_vga.set_value(40);
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(2000000);
receiver_model.set_baseband_bandwidth(2500000);

View File

@ -22,14 +22,11 @@
#include "ui.hpp"
#include "file.hpp"
//#include "ui_textentry.hpp"
//#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "recent_entries.hpp"
#include "message.hpp"
#include "portapack.hpp"
namespace ui {
@ -38,9 +35,12 @@ struct ADSBRecentEntry {
static constexpr Key invalid_key = 0xffffffff;
uint32_t ICAO_address;
uint32_t ICAO_address { };
uint16_t hits { 0 };
uint8_t raw_data[14] { }; // 112 bits at most
std::string callsign { " " };
std::string time { "" };
std::string geo_pos { "" };
ADSBRecentEntry(
) : ADSBRecentEntry { 0 }
@ -57,6 +57,18 @@ struct ADSBRecentEntry {
return ICAO_address;
}
void set_callsign(std::string& new_callsign) {
callsign = new_callsign;
}
void inc_hit() {
hits++;
}
void set_pos(std::string& new_pos) {
geo_pos = new_pos;
}
void set_time(std::string& new_time) {
time = new_time;
}
@ -75,34 +87,34 @@ public:
void focus() override;
std::string title() const override { return "ADS-B debug"; };
std::string title() const override { return "ADS-B receive"; };
private:
//static constexpr float k = 1.0f / 128.0f;
//File iq_file { };
//size_t f_offset { 0 };
//bool analyze(uint64_t offset);
void on_frame(const ADSBFrameMessage * message);
const RecentEntriesColumns columns { {
{ "Raw", 21 },
{ "Time", 8 },
{ "ICAO", 6 },
{ "Callsign", 8 },
{ "Hits", 5 },
{ "Time", 8 }
} };
ADSBRecentEntries recent { };
RecentEntriesView<RecentEntries<ADSBRecentEntry>> recent_entries_view { columns, recent };
/*Labels labels {
{ { 0 * 8, 0 * 8 }, "Test", Color::light_grey() }
};*/
RSSI rssi {
{ 19 * 8, 4, 10 * 8, 8 },
};
NumberField offset_field {
{ 0, 0 },
6,
{ 0, 819200 }, // * 256 -> file offset
1,
'0'
LNAGainField field_lna {
{ 4 * 8, 0 * 16 }
};
VGAGainField field_vga {
{ 11 * 8, 0 * 16 }
};
Labels labels {
{ { 0 * 8, 0 * 8 }, "LNA: VGA: RSSI:", Color::light_grey() }
};
Text text_debug_a {
@ -118,11 +130,6 @@ private:
"-"
};
/*Button button_ffw {
{ 184, 0 * 16, 56, 16 },
"FFW"
};*/
MessageHandlerRegistration message_handler_frame {
Message::ID::ADSBFrame,
[this](Message* const p) {

View File

@ -73,28 +73,10 @@ void Compass::paint(Painter&) {
set_value(value_);
}
ADSBView::ADSBView() {
add_child(&check_enable);
hidden(true);
check_enable.on_select = [this](Checkbox&, bool value) {
enabled = value;
};
}
void ADSBView::set_enabled(bool value) {
check_enable.set_value(value);
}
void ADSBView::set_type(std::string type) {
check_enable.set_text("Transmit " + type);
}
void ADSBView::focus() {
check_enable.focus();
}
ADSBPositionView::ADSBPositionView(NavigationView& nav) {
ADSBPositionView::ADSBPositionView(
NavigationView& nav, Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("position");
add_children({
@ -133,7 +115,10 @@ void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector<A
frame_list.emplace_back(temp_frame);
}
ADSBCallsignView::ADSBCallsignView(NavigationView& nav) {
ADSBCallsignView::ADSBCallsignView(
NavigationView& nav, Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("callsign");
add_children({
@ -146,7 +131,14 @@ ADSBCallsignView::ADSBCallsignView(NavigationView& nav) {
button_callsign.set_text(callsign);
button_callsign.on_select = [this, &nav](Button&) {
text_prompt(nav, &callsign, 8);
text_prompt(
nav,
&callsign,
8,
[this](std::string* s) {
button_callsign.set_text(*s);
}
);
};
}
@ -160,7 +152,10 @@ void ADSBCallsignView::collect_frames(const uint32_t ICAO_address, std::vector<A
frame_list.emplace_back(temp_frame);
}
ADSBSpeedView::ADSBSpeedView() {
ADSBSpeedView::ADSBSpeedView(
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("speed");
add_children({
@ -189,7 +184,10 @@ void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector<ADSB
frame_list.emplace_back(temp_frame);
}
ADSBSquawkView::ADSBSquawkView() {
ADSBSquawkView::ADSBSquawkView(
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("squawk");
add_children({
@ -325,7 +323,6 @@ ADSBTxView::ADSBTxView(
NavigationView& nav
) : nav_ { nav }
{
Rect view_rect = { 0, 7 * 8, 240, 192 };
baseband::run_image(portapack::spi_flash::image_tag_adsb_tx);
add_children({
@ -340,11 +337,6 @@ ADSBTxView::ADSBTxView(
&tx_view
});
view_position.set_parent_rect(view_rect);
view_callsign.set_parent_rect(view_rect);
view_speed.set_parent_rect(view_rect);
view_squawk.set_parent_rect(view_rect);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {

View File

@ -22,14 +22,10 @@
#include "ui.hpp"
#include "adsb.hpp"
#include "utility.hpp"
#include "ui_textentry.hpp"
#include "ui_widget.hpp"
#include "ui_geomap.hpp"
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
#include "transmitter_model.hpp"
#include "portapack.hpp"
@ -51,32 +47,9 @@ private:
uint32_t value_ { 0 };
};
class ADSBView : public View {
class ADSBPositionView : public OptionTabView {
public:
ADSBView();
void focus() override;
void set_type(std::string type);
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
protected:
bool enabled { false };
void set_enabled(bool value);
private:
Checkbox check_enable {
{ 2 * 8, 0 * 16 },
20,
"",
false
};
};
class ADSBPositionView : public ADSBView {
public:
ADSBPositionView(NavigationView& nav);
ADSBPositionView(NavigationView& nav, Rect parent_rect);
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
@ -91,9 +64,9 @@ private:
};
};
class ADSBCallsignView : public ADSBView {
class ADSBCallsignView : public OptionTabView {
public:
ADSBCallsignView(NavigationView& nav);
ADSBCallsignView(NavigationView& nav, Rect parent_rect);
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
@ -110,9 +83,9 @@ private:
};
};
class ADSBSpeedView : public ADSBView {
class ADSBSpeedView : public OptionTabView {
public:
ADSBSpeedView();
ADSBSpeedView(Rect parent_rect);
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
@ -134,9 +107,9 @@ private:
};
};
class ADSBSquawkView : public ADSBView {
class ADSBSquawkView : public OptionTabView {
public:
ADSBSquawkView();
ADSBSquawkView(Rect parent_rect);
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
@ -223,10 +196,12 @@ private:
void start_tx();
void generate_frames();
ADSBPositionView view_position { nav_ };
ADSBCallsignView view_callsign { nav_ };
ADSBSpeedView view_speed { };
ADSBSquawkView view_squawk { };
Rect view_rect = { 0, 7 * 8, 240, 192 };
ADSBPositionView view_position { nav_, view_rect };
ADSBCallsignView view_callsign { nav_, view_rect };
ADSBSpeedView view_speed { view_rect };
ADSBSquawkView view_squawk { view_rect };
TabView tab_view {
{ "Position", Color::cyan(), &view_position },

View File

@ -190,8 +190,6 @@ void EncodersView::start_tx(const bool scan) {
transmitter_model.set_sampling_rate(2280000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();

View File

@ -282,7 +282,7 @@ void NavigationView::focus() {
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
add_items({
{ "ADS-B: Planes", ui::Color::yellow(),&bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, },
{ "ADS-B: Planes", ui::Color::green(),&bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, },
{ "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } },
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } },
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(false); } },
@ -301,7 +301,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
add_items({
{ "ADS-B Mode S", ui::Color::orange(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBTxView>(); } },
{ "ADS-B Mode S", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBTxView>(); } },
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSTXView>(); } },
{ "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push<BHTView>(); } },
{ "Jammer", ui::Color::yellow(), &bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },

View File

@ -33,12 +33,145 @@ using namespace rds;
namespace ui {
void RDSView::focus() {
button_editpsn.focus();
RDSPSNView::RDSPSNView(
NavigationView& nav,
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("PSN");
add_children({
&labels,
&text_psn,
&button_set,
&check_mono_stereo,
&check_TA,
&check_MS
});
set_enabled(true);
check_TA.set_value(true);
check_mono_stereo.on_select = [this](Checkbox&, bool value) {
mono_stereo = value;
};
check_TA.on_select = [this](Checkbox&, bool value) {
TA = value;
};
check_MS.on_select = [this](Checkbox&, bool value) {
MS = value;
};
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
&PSN,
8,
[this](std::string* s) {
text_psn.set(*s);
}
);
};
}
void RDSView::on_tuning_frequency_changed(rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
RDSRadioTextView::RDSRadioTextView(
NavigationView& nav,
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("Radiotext");
add_children({
&labels,
&button_set,
&text_radiotext
});
button_set.on_select = [this, &nav](Button&){
text_prompt(
nav,
&radiotext,
28,
[this](std::string* s) {
text_radiotext.set(*s);
}
);
};
}
RDSDateTimeView::RDSDateTimeView(
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("date & time");
add_children({
&labels
});
}
RDSAudioView::RDSAudioView(
Rect parent_rect
) : OptionTabView(parent_rect)
{
set_type("audio");
add_children({
&labels
});
}
RDSThread::RDSThread(
std::vector<RDSGroup>** frames
) : frames_ { std::move(frames) }
{
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, RDSThread::static_fn, this);
}
RDSThread::~RDSThread() {
if( thread ) {
chThdTerminate(thread);
chThdWait(thread);
thread = nullptr;
}
}
msg_t RDSThread::static_fn(void* arg) {
auto obj = static_cast<RDSThread*>(arg);
obj->run();
return 0;
}
void RDSThread::run() {
std::vector<RDSGroup>* frame_ptr;
size_t block_count, c;
uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
uint32_t frame_index = 0;
while( !chThdShouldTerminate() ) {
do {
frame_ptr = frames_[frame_index];
if (frame_index == 2) {
frame_index = 0;
} else {
frame_index++;
}
} while(!(block_count = frame_ptr->size() * 4));
for (c = 0; c < block_count; c++)
tx_data_u32[c] = frame_ptr->at(c >> 2).block[c & 3];
baseband::set_rds_data(block_count * 26);
chThdSleepMilliseconds(1000);
}
}
void RDSView::focus() {
tab_view.focus();
}
RDSView::~RDSView() {
@ -47,80 +180,58 @@ RDSView::~RDSView() {
}
void RDSView::start_tx() {
rds_flags.PI_code = sym_pi_code.value_hex_u64();
rds_flags.PTY = options_pty.selected_index_value();
rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
rds_flags.TP = check_TP.value();
rds_flags.TA = view_PSN.TA;
rds_flags.MS = view_PSN.MS;
if (view_PSN.is_enabled())
gen_PSN(frame_psn, view_PSN.PSN, &rds_flags);
else
frame_psn.clear();
if (view_radiotext.is_enabled())
gen_RadioText(frame_radiotext, view_radiotext.radiotext, 0, &rds_flags);
else
frame_radiotext.clear();
// DEBUG
if (view_datetime.is_enabled())
gen_ClockTime(frame_datetime, &rds_flags, 2016, 12, 1, 9, 23, 2);
else
frame_datetime.clear();
transmitter_model.set_sampling_rate(2280000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_rds_data(message_length);
tx_thread = std::make_unique<RDSThread>(frames);
}
void RDSView::paint(Painter&) {
text_psn.set(PSN);
text_radiotexta.set(RadioText.substr(0, 16));
text_radiotextb.set(RadioText.substr(16, 16));
}
RDSView::RDSView(NavigationView& nav) {
RDSView::RDSView(
NavigationView& nav
) : nav_ { nav }
{
baseband::run_image(portapack::spi_flash::image_tag_rds);
PSN = "TEST1234";
RadioText = "Radiotext test ABCD1234";
add_children({
&tab_view,
&labels,
&options_pty,
&options_countrycode,
&options_coverage,
&options_tx,
&check_mono_stereo,
&check_TA,
&check_TP,
&check_MS,
&sym_pi_code,
&button_editpsn,
&text_psn,
&button_editradiotext,
&text_radiotexta,
&text_radiotextb,
&check_TP,
&options_pty,
&view_PSN,
&view_radiotext,
&view_datetime,
&view_audio,
//&options_countrycode,
//&options_coverage,
&tx_view,
});
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
this->on_tuning_frequency_changed(f);
};
};
tx_view.on_start = [this]() {
tx_view.set_transmitting(true);
rds_flags.PI_code = sym_pi_code.value_hex_u64();
rds_flags.PTY = options_pty.selected_index_value();
rds_flags.DI = check_mono_stereo.value() ? 1 : 0;
rds_flags.TP = check_TP.value();
rds_flags.TA = check_TA.value();
rds_flags.MS = check_MS.value();
if (options_tx.selected_index() == 0)
message_length = gen_PSN(PSN, &rds_flags);
else if (options_tx.selected_index() == 1)
message_length = gen_RadioText(RadioText, 0, &rds_flags);
else
message_length = gen_ClockTime(&rds_flags, 2016, 12, 1, 9, 23, 2);
txing = true;
start_tx();
};
tx_view.on_stop = [this]() {
tx_view.set_transmitting(false);
transmitter_model.disable();
txing = false;
};
check_TA.set_value(true);
check_TP.set_value(true);
sym_pi_code.set_sym(0, 0xF);
@ -132,15 +243,26 @@ RDSView::RDSView(NavigationView& nav) {
};
options_pty.set_selected_index(0); // None
options_countrycode.set_selected_index(18); // Baguette du fromage
options_coverage.set_selected_index(0); // Local
//options_countrycode.set_selected_index(18); // Baguette du fromage
//options_coverage.set_selected_index(0); // Local
button_editpsn.on_select = [this, &nav](Button&) {
text_prompt(nav, &PSN, 8);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
};
};
button_editradiotext.on_select = [this, &nav](Button&){
text_prompt(nav, &RadioText, 24);
tx_view.on_start = [this]() {
start_tx();
tx_view.set_transmitting(true);
txing = true;
};
tx_view.on_stop = [this]() {
tx_view.set_transmitting(false);
transmitter_model.disable();
txing = false;
};
}

View File

@ -21,52 +21,169 @@
*/
#include "ui.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "ui_textentry.hpp"
#include "message.hpp"
#include "ui_tabview.hpp"
#include "rds.hpp"
using namespace rds;
namespace ui {
class RDSPSNView : public OptionTabView {
public:
RDSPSNView(NavigationView& nav, Rect parent_rect);
std::string PSN { "TEST1234" };
bool mono_stereo { false };
bool TA { false };
bool MS { false };
private:
Labels labels {
{ { 1 * 8, 3 * 8 }, "Program Service Name", Color::light_grey() },
{ { 2 * 8, 7 * 8 }, "PSN:", Color::light_grey() }
};
Button button_set {
{ 18 * 8, 3 * 16, 80, 32 },
"Set"
};
Text text_psn {
{ 6 * 8, 3 * 16 + 8, 8 * 8, 16 },
""
};
Checkbox check_mono_stereo {
{ 2 * 8, 12 * 8 },
6,
"Stereo"
};
Checkbox check_MS {
{ 14 * 8, 12 * 8 },
5,
"Music"
};
Checkbox check_TA {
{ 2 * 8, 16 * 8 },
20,
"Traffic announcement"
};
};
class RDSRadioTextView : public OptionTabView {
public:
RDSRadioTextView(NavigationView& nav, Rect parent_rect);
std::string radiotext { "Radiotext test ABCD1234" };
private:
Labels labels {
{ { 2 * 8, 3 * 8 }, "Radiotext", Color::light_grey() },
{ { 1 * 8, 6 * 8 }, "Text:", Color::light_grey() }
};
Text text_radiotext {
{ 1 * 8, 4 * 16, 28 * 8, 16 },
"-"
};
Button button_set {
{ 88, 6 * 16, 64, 32 },
"Set"
};
};
class RDSDateTimeView : public OptionTabView {
public:
RDSDateTimeView(Rect parent_rect);
private:
Labels labels {
{ { 44, 5 * 16 }, "Not yet implemented", Color::red() }
};
};
class RDSAudioView : public OptionTabView {
public:
RDSAudioView(Rect parent_rect);
private:
Labels labels {
{ { 44, 5 * 16 }, "Not yet implemented", Color::red() }
};
};
class RDSThread {
public:
RDSThread(std::vector<RDSGroup>** frames);
~RDSThread();
RDSThread(const RDSThread&) = delete;
RDSThread(RDSThread&&) = delete;
RDSThread& operator=(const RDSThread&) = delete;
RDSThread& operator=(RDSThread&&) = delete;
private:
std::vector<RDSGroup>** frames_ { };
Thread* thread { nullptr };
static msg_t static_fn(void* arg);
void run();
};
class RDSView : public View {
public:
RDSView(NavigationView& nav);
~RDSView();
RDSView(const RDSView&) = delete;
RDSView(RDSView&&) = delete;
RDSView& operator=(const RDSView&) = delete;
RDSView& operator=(RDSView&&) = delete;
void focus() override;
void paint(Painter& painter) override;
std::string title() const override { return "RDS transmit"; };
private:
std::string PSN { 0 };
std::string RadioText { 0 };
bool txing = false;
NavigationView& nav_;
RDS_flags rds_flags { };
std::vector<RDSGroup> frame_psn { };
std::vector<RDSGroup> frame_radiotext { };
std::vector<RDSGroup> frame_datetime { };
std::vector<RDSGroup>* frames[3] { &frame_psn, &frame_radiotext, &frame_datetime };
bool txing = false;
uint16_t message_length { 0 };
void start_tx();
void on_tuning_frequency_changed(rf::Frequency f);
Rect view_rect = { 0, 8 * 8, 240, 192 };
RDSPSNView view_PSN { nav_, view_rect };
RDSRadioTextView view_radiotext { nav_, view_rect };
RDSDateTimeView view_datetime { view_rect };
RDSAudioView view_audio { view_rect };
TabView tab_view {
{ "Name", Color::cyan(), &view_PSN },
{ "Text", Color::green(), &view_radiotext },
{ "Time", Color::yellow(), &view_datetime },
{ "Audio", Color::orange(), &view_audio }
};
Labels labels {
{ { 1 * 8, 16 + 8 }, "PTY:", Color::light_grey() },
{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
{ { 1 * 8, 32 + 8 }, "PI:", Color::light_grey() },
{ { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() },
{ { 2 * 8, 13 * 8 }, "PSN:", Color::light_grey() },
{ { 2 * 8, 8 * 16 }, "RadioText:", Color::light_grey() },
{ { 2 * 8, 14 * 16 }, "TX group:", Color::light_grey() }
{ { 0 * 8, 28 }, "Program type:", Color::light_grey() },
//{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
{ { 2 * 8, 28 + 16 }, "Program ID:", Color::light_grey() },
//{ { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() },
};
OptionsField options_pty {
{ 5 * 8, 16 + 8 },
{ 13 * 8, 28 },
8,
{
{ "None", 0 },
@ -104,7 +221,7 @@ private:
}
};
OptionsField options_countrycode {
/*OptionsField options_countrycode {
{ 17 * 8, 16 + 8 },
11,
{
@ -171,15 +288,15 @@ private:
{ "Vatican", 4 },
{ "Yugoslavia", 13 }
}
};
};*/
SymField sym_pi_code {
{ 4 * 8, 32 + 8 },
{ 13 * 8, 28 + 16 },
4,
SymField::SYMFIELD_HEX
};
OptionsField options_coverage {
/*OptionsField options_coverage {
{ 17 * 8, 32 + 8 },
12,
{
@ -200,66 +317,21 @@ private:
{ "R111", 14 },
{ "R112", 15 }
}
};
};*/
Checkbox check_mono_stereo {
{ 1 * 8, 4 * 16 },
6,
"Stereo"
};
Checkbox check_TA {
{ 12 * 8, 4 * 16 },
2,
"TA"
};
Checkbox check_TP {
{ 18 * 8, 4 * 16 },
{ 23 * 8, 4 * 8 },
2,
"TP"
};
Checkbox check_MS {
{ 24 * 8, 4 * 16 },
2,
"MS"
};
Button button_editpsn {
{ 22 * 8, 5 * 16 + 20, 48, 24 },
"Set"
};
Text text_psn {
{ 6 * 8, 13 * 8, 4 * 8, 16 },
""
};
Text text_radiotexta {
{ 2 * 8, 9 * 16, 19 * 8, 16 },
"-"
};
Text text_radiotextb {
{ 2 * 8, 10 * 16, 19 * 8, 16 },
"-"
};
Button button_editradiotext {
{ 22 * 8, 8 * 16 + 12, 48, 24 },
"Set"
};
OptionsField options_tx {
{ 11 * 8, 14 * 16 },
11,
{
{ "PSN", 0 },
{ "RadioText", 1 },
{ "Date & time", 2 }
}
};
TransmitterView tx_view {
16 * 16,
50000,
9
};
std::unique_ptr<RDSThread> tx_thread { };
};
} /* namespace ui */

View File

@ -141,7 +141,7 @@ TransmitterView::TransmitterView(
on_edit_frequency();
};
field_frequency.on_change = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
receiver_model.set_tuning_frequency(f);
};
button_start.on_select = [this](Button&){

View File

@ -25,6 +25,7 @@
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "adsb_frame.hpp"
@ -44,6 +45,7 @@ private:
static constexpr size_t baseband_fs = 2000000;
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
RSSIThread rssi_thread { NORMALPRIO + 10 };
ADSBFrame frame { };
bool configured { false };

View File

@ -70,6 +70,27 @@ void encode_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::s
frame.make_CRC();
}
std::string decode_frame_id(ADSBFrame& frame) {
std::string callsign = "";
uint8_t * raw_data = frame.get_raw_data();
uint64_t callsign_coded = 0;
uint32_t c;
// Frame bytes to long
for (c = 5; c < 11; c++) {
callsign_coded <<= 8;
callsign_coded |= raw_data[c];
}
// Long to 6-bit characters
for (c = 0; c < 8; c++) {
callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]);
callsign_coded <<= 6;
}
return callsign;
}
/*void generate_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code) {
make_frame_mode_s(frame, ICAO_address);
@ -141,26 +162,6 @@ int cpr_N(float lat, int is_odd) {
return nl;
}
// Decoding method (from dump1090):
// index int j = floor(((59 * latcprE - 60 * latcprO) / 131072) + 0.50)
// latE = DlatE * (cpr_mod(j, 60) + (latcprE / 131072))
// latO = DlatO * (cpr_mod(j, 59) + (latcprO / 131072))
// if latE >= 270 -> latE -= 360
// if latO >= 270 -> latO -= 360
// if (cpr_NL(latE) != cpr_NL(latO)) return;
// int ni = cpr_N(latE ,0);
// int m = floor((((loncprE * (cpr_NL(latE) - 1)) - (loncprO * cpr_NL(latE))) / 131072) + 0.5)
// lon = cpr_Dlon(latE, 0) * (cpr_mod(m, ni) + loncprE / 131072);
// lat = latE;
// ... or ...
// int ni = cpr_N(latO ,0);
// int m = floor((((loncprE * (cpr_NL(latO) - 1)) - (loncprO * cpr_NL(latO))) / 131072) + 0.5)
// lon = cpr_Dlon(latO, 0) * (cpr_mod(m, ni) + loncprO / 131072);
// lat = latO;
// ... and ...
// if (lon > 180) lon -= 360;
void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32_t altitude,
const float latitude, const float longitude, const uint32_t time_parity) {
@ -202,6 +203,37 @@ void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32
frame.make_CRC();
}
// Decoding method (from dump1090):
// index int j = floor(((59 * latcprE - 60 * latcprO) / 131072) + 0.50)
// latE = DlatE * (cpr_mod(j, 60) + (latcprE / 131072))
// latO = DlatO * (cpr_mod(j, 59) + (latcprO / 131072))
// if latE >= 270 -> latE -= 360
// if latO >= 270 -> latO -= 360
// if (cpr_NL(latE) != cpr_NL(latO)) return;
// int ni = cpr_N(latE ,0);
// int m = floor((((loncprE * (cpr_NL(latE) - 1)) - (loncprO * cpr_NL(latE))) / 131072) + 0.5)
// lon = cpr_Dlon(latE, 0) * (cpr_mod(m, ni) + loncprE / 131072);
// lat = latE;
// ... or ...
// int ni = cpr_N(latO ,0);
// int m = floor((((loncprE * (cpr_NL(latO) - 1)) - (loncprO * cpr_NL(latO))) / 131072) + 0.5)
// lon = cpr_Dlon(latO, 0) * (cpr_mod(m, ni) + loncprO / 131072);
// lat = latO;
// ... and ...
// if (lon > 180) lon -= 360;
// Only altitude is decoded for now
uint32_t decode_frame_pos(ADSBFrame& frame) {
uint8_t * raw_data = frame.get_raw_data();
// Q-bit is present
if (raw_data[5] & 1)
return ((((raw_data[5] >> 1) << 4) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
return 0;
}
// speed is in knots
// vertical rate is in ft/min
void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed,

View File

@ -68,12 +68,19 @@ const float adsb_lat_lut[58] = {
};
void make_frame_adsb(ADSBFrame& frame, const uint32_t ICAO_address);
void encode_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::string& callsign);
std::string decode_frame_id(ADSBFrame& frame);
void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32_t altitude,
const float latitude, const float longitude, const uint32_t time_parity);
uint32_t decode_frame_pos(ADSBFrame& frame);
void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed,
const float angle, const int32_t v_rate);
//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);
} /* namespace adsb */

View File

@ -60,39 +60,41 @@ public:
uint8_t * get_raw_data() {
return raw_data;
}
std::string get_callsign() {
uint64_t callsign_coded = 0;
uint32_t c;
std::string callsign = "";
// Frame bytes to long
for (c = 5; c < 11; c++) {
callsign_coded <<= 8;
callsign_coded |= raw_data[c];
}
// Long to 6-bit characters
for (c = 0; c < 8; c++) {
callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]);
callsign_coded <<= 6;
}
return callsign;
}
void make_CRC() {
uint8_t adsb_crc[14]; // Temp buffer
uint32_t computed_CRC = compute_CRC();
// Insert CRC in frame
raw_data[11] = (computed_CRC >> 16) & 0xFF;
raw_data[12] = (computed_CRC >> 8) & 0xFF;
raw_data[13] = computed_CRC & 0xFF;
}
bool check_CRC() {
uint32_t computed_CRC = compute_CRC();
if (raw_data[11] != ((computed_CRC >> 16) & 0xFF)) return false;
if (raw_data[12] != ((computed_CRC >> 8) & 0xFF)) return false;
if (raw_data[13] != (computed_CRC & 0xFF)) return false;
return true;
}
private:
static const uint8_t adsb_preamble[16];
static const char icao_id_lut[65];
alignas(4) uint8_t index { 0 };
alignas(4) uint8_t raw_data[14] { }; // 112 bits at most
uint32_t compute_CRC() {
uint8_t adsb_crc[14] = { 0 }; // Temp buffer
uint8_t b, c, s, bitn;
const uint32_t crc_poly = 0x1205FFF;
// Clear CRC
raw_data[11] = 0x00;
raw_data[12] = 0x00;
raw_data[13] = 0x00;
// Copy frame data
memcpy(adsb_crc, raw_data, 11);
// Compute CRC
memcpy(adsb_crc, raw_data, 14);
for (c = 0; c < 11; c++) {
for (b = 0; b < 8; b++) {
if ((adsb_crc[c] << b) & 0x80) {
@ -104,15 +106,8 @@ public:
}
}
// Insert CRC in frame
memcpy(&raw_data[11], &adsb_crc[11], 3);
return (adsb_crc[11] << 16) + (adsb_crc[12] << 8) + adsb_crc[13];
}
private:
static const uint8_t adsb_preamble[16];
static const char icao_id_lut[65];
alignas(4) uint8_t index { 0 };
alignas(4) uint8_t raw_data[14] { }; // 112 bits at most
};
} /* namespace adsb */

View File

@ -276,6 +276,35 @@ std::string View::title() const {
return "";
};
/* OptionTabView *********************************************************/
OptionTabView::OptionTabView(Rect parent_rect) {
set_parent_rect(parent_rect);
add_child(&check_enable);
hidden(true);
check_enable.on_select = [this](Checkbox&, bool value) {
enabled = value;
};
}
void OptionTabView::set_enabled(bool value) {
check_enable.set_value(value);
}
bool OptionTabView::is_enabled() {
return check_enable.value();
}
void OptionTabView::set_type(std::string type) {
check_enable.set_text("Transmit " + type);
}
void OptionTabView::focus() {
check_enable.focus();
}
/* Rectangle *************************************************************/
Rectangle::Rectangle(

View File

@ -603,6 +603,29 @@ private:
bool show_max_;
};
class OptionTabView : public View {
public:
OptionTabView(Rect parent_rect);
void focus() override;
bool is_enabled();
void set_type(std::string type);
protected:
bool enabled { false };
void set_enabled(bool value);
private:
Checkbox check_enable {
{ 2 * 8, 0 * 16 },
20,
"",
false
};
};
} /* namespace ui */
#endif/*__UI_WIDGET_H__*/

Binary file not shown.