mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-13 19:54:39 +00:00
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:
parent
160359166a
commit
9d902bc224
@ -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 */
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 },
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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>(); } },
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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&){
|
||||
|
@ -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 };
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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(
|
||||
|
@ -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.
Loading…
Reference in New Issue
Block a user