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"
|
#include "portapack_shared_memory.hpp"
|
||||||
|
|
||||||
// RDS infos:
|
// RDS infos:
|
||||||
|
// One frame = X groups (as necessary)
|
||||||
// One group = 4 blocks = 4 * 26 bits
|
// One group = 4 blocks = 4 * 26 bits
|
||||||
// One block = 26 bits (16 bits data + 10 bits checkword)
|
// One block = 26 bits (16 bits data + 10 bits checkword)
|
||||||
// Sent MSB first
|
// Sent MSB first
|
||||||
@ -44,11 +45,6 @@ uint32_t make_block(uint32_t data, uint16_t offset) {
|
|||||||
return (data << 10) | (CRC ^ offset);
|
return (data << 10) | (CRC ^ offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo:
|
|
||||||
// Make PI
|
|
||||||
// TA/TP flags
|
|
||||||
// Group selection
|
|
||||||
|
|
||||||
// Boolean to binary
|
// Boolean to binary
|
||||||
uint8_t b2b(const bool in) {
|
uint8_t b2b(const bool in) {
|
||||||
if (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
|
// 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) {
|
const bool MS, const bool DI, const uint8_t C, const std::string chars) {
|
||||||
group->block[0] = PI_code;
|
RDSGroup group;
|
||||||
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[0] = PI_code;
|
||||||
group->block[3] = (chars[0] << 8) | chars[1];
|
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
|
// 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) {
|
const uint8_t segment, const std::string chars) {
|
||||||
group->block[0] = PI_code;
|
RDSGroup group;
|
||||||
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[0] = PI_code;
|
||||||
group->block[3] = (chars[2] << 8) | chars[3];
|
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
|
// 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 uint16_t year, const uint8_t month, const uint8_t day,
|
||||||
const uint8_t hour, const uint8_t minute, const int8_t local_offset) {
|
const uint8_t hour, const uint8_t minute, const int8_t local_offset) {
|
||||||
|
RDSGroup group;
|
||||||
uint32_t L = 0;
|
uint32_t L = 0;
|
||||||
uint32_t day_code;
|
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);
|
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[0] = PI_code;
|
||||||
group->block[1] = (0x4 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | ((day_code & 0x18000) >> 15);
|
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[2] = ((day_code & 0x7FFF) << 1) | (hour >> 4);
|
||||||
group->block[3] = ((hour & 15) << 12) | ((minute & 0x3F) << 6) | (local_offset & 0x3F);
|
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;
|
uint8_t c;
|
||||||
RDS_group groups[4] = { 0 };
|
RDSGroup group;
|
||||||
|
|
||||||
|
frame.clear();
|
||||||
|
|
||||||
// 4 groups with 2 PSN characters in each
|
// 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++) {
|
for (c = 0; c < 4; c++) {
|
||||||
groups[c].block[0] = make_block(groups[c].block[0], RDS_OFFSET_A);
|
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));
|
||||||
groups[c].block[1] = make_block(groups[c].block[1], RDS_OFFSET_B);
|
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
|
||||||
groups[c].block[2] = make_block(groups[c].block[2], RDS_OFFSET_Cp); // C' !
|
group.block[1] = make_block(group.block[1], RDS_OFFSET_B);
|
||||||
groups[c].block[3] = make_block(groups[c].block[3], RDS_OFFSET_D);
|
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;
|
void gen_RadioText(std::vector<RDSGroup>& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags) {
|
||||||
|
|
||||||
// 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) {
|
|
||||||
size_t c;
|
size_t c;
|
||||||
RDS_group * groups_ptr;
|
//RDSGroup * groups_ptr;
|
||||||
std::string radiotext_buffer = text;
|
std::string radiotext_buffer = text;
|
||||||
size_t rt_length, groups;
|
size_t rt_length, group_count;
|
||||||
|
RDSGroup group;
|
||||||
|
|
||||||
radiotext_buffer += 0x0D;
|
radiotext_buffer += 0x0D;
|
||||||
rt_length = radiotext_buffer.length();
|
rt_length = radiotext_buffer.length();
|
||||||
rt_length = (rt_length + 3) & 0xFC;
|
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++)
|
frame.clear();
|
||||||
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 < group_count; c++) {
|
||||||
for (c = 0; c < groups; c++) {
|
group = make_2A_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, AB, c, radiotext_buffer.substr(c * 4, 4));
|
||||||
groups_ptr[c].block[0] = make_block(groups_ptr[c].block[0], RDS_OFFSET_A);
|
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
|
||||||
groups_ptr[c].block[1] = make_block(groups_ptr[c].block[1], RDS_OFFSET_B);
|
group.block[1] = make_block(group.block[1], RDS_OFFSET_B);
|
||||||
groups_ptr[c].block[2] = make_block(groups_ptr[c].block[2], RDS_OFFSET_C);
|
group.block[2] = make_block(group.block[2], RDS_OFFSET_C);
|
||||||
groups_ptr[c].block[3] = make_block(groups_ptr[c].block[3], RDS_OFFSET_D);
|
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;
|
void gen_ClockTime(std::vector<RDSGroup>& frame, const RDS_flags * rds_flags,
|
||||||
|
|
||||||
// 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,
|
|
||||||
const uint16_t year, const uint8_t month, const uint8_t day,
|
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) {
|
const uint8_t hour, const uint8_t minute, const int8_t local_offset) {
|
||||||
uint8_t c;
|
RDSGroup group;
|
||||||
RDS_group group = { 0 };
|
|
||||||
|
|
||||||
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
|
// Generate checkbits for each block
|
||||||
group.block[0] = make_block(group.block[0], RDS_OFFSET_A);
|
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[2] = make_block(group.block[2], RDS_OFFSET_C);
|
||||||
group.block[3] = make_block(group.block[3], RDS_OFFSET_D);
|
group.block[3] = make_block(group.block[3], RDS_OFFSET_D);
|
||||||
|
|
||||||
uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
|
frame.clear();
|
||||||
|
frame.emplace_back(group);
|
||||||
// Copy to tx_data for baseband
|
|
||||||
for (c = 0; c < 4; c++)
|
|
||||||
tx_data_u32[c] = group.block[c];
|
|
||||||
|
|
||||||
return 4 * 26;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace rds */
|
} /* namespace rds */
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
* Boston, MA 02110-1301, USA.
|
* Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include "ch.h"
|
#include "ch.h"
|
||||||
|
|
||||||
#ifndef __RDS_H__
|
#ifndef __RDS_H__
|
||||||
@ -44,24 +44,24 @@ struct RDS_flags {
|
|||||||
bool MS;
|
bool MS;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RDS_group {
|
struct RDSGroup {
|
||||||
uint32_t block[4];
|
uint32_t block[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t make_block(uint32_t blockdata, uint16_t offset);
|
uint32_t make_block(uint32_t blockdata, uint16_t offset);
|
||||||
uint8_t b2b(const bool in);
|
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);
|
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);
|
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 uint16_t year, const uint8_t month, const uint8_t day,
|
||||||
const uint8_t hour, const uint8_t minute, const int8_t local_offset);
|
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);
|
void gen_PSN(std::vector<RDSGroup>& frame, 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);
|
void gen_RadioText(std::vector<RDSGroup>& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags);
|
||||||
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 uint16_t year, const uint8_t month, const uint8_t day,
|
||||||
const uint8_t hour, const uint8_t minute, const int8_t local_offset);
|
const uint8_t hour, const uint8_t minute, const int8_t local_offset);
|
||||||
|
|
||||||
|
@ -45,12 +45,16 @@ void RecentEntriesTable<ADSBRecentEntries>::draw(
|
|||||||
Painter& painter,
|
Painter& painter,
|
||||||
const Style& style
|
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() {
|
void ADSBRxView::focus() {
|
||||||
offset_field.focus();
|
field_lna.focus();
|
||||||
offset_field.set_value(13179);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBRxView::~ADSBRxView() {
|
ADSBRxView::~ADSBRxView() {
|
||||||
@ -58,205 +62,50 @@ ADSBRxView::~ADSBRxView() {
|
|||||||
baseband::shutdown();
|
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) {
|
void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||||
rtc::RTC datetime;
|
rtc::RTC datetime;
|
||||||
std::string str_timestamp;
|
std::string str_timestamp;
|
||||||
|
std::string callsign;
|
||||||
|
|
||||||
auto frame = message->frame;
|
auto frame = message->frame;
|
||||||
auto& entry = ::on_packet(recent, frame.get_ICAO_address());
|
|
||||||
|
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);
|
rtcGetTime(&RTCD1, &datetime);
|
||||||
str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
|
str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
|
||||||
to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
|
to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
|
||||||
to_string_dec_uint(datetime.second(), 2, '0');
|
to_string_dec_uint(datetime.second(), 2, '0');
|
||||||
|
|
||||||
entry.set_time(str_timestamp);
|
entry.set_time(str_timestamp);
|
||||||
entry.set_raw(frame.get_raw_data());
|
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();
|
recent_entries_view.set_dirty();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ADSBRxView::ADSBRxView(NavigationView& nav) {
|
ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
|
baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
//&labels,
|
&labels,
|
||||||
&offset_field,
|
&rssi,
|
||||||
//&button_ffw,
|
&field_lna,
|
||||||
|
&field_vga,
|
||||||
&text_debug_a,
|
&text_debug_a,
|
||||||
&text_debug_b,
|
&text_debug_b,
|
||||||
&text_debug_c,
|
&text_debug_c,
|
||||||
@ -264,29 +113,16 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
recent_entries_view.set_parent_rect({ 0, 64, 240, 224 });
|
recent_entries_view.set_parent_rect({ 0, 64, 240, 224 });
|
||||||
|
recent_entries_view.on_select = [this](const ADSBRecentEntry& entry) {
|
||||||
// File must be 16bit complex @ 2Msps !
|
text_debug_a.set(entry.geo_pos);
|
||||||
/*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++;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
baseband::set_adsb();
|
baseband::set_adsb();
|
||||||
|
|
||||||
receiver_model.set_tuning_frequency(1090000000);
|
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_modulation(ReceiverModel::Mode::SpectrumAnalysis);
|
||||||
receiver_model.set_sampling_rate(2000000);
|
receiver_model.set_sampling_rate(2000000);
|
||||||
receiver_model.set_baseband_bandwidth(2500000);
|
receiver_model.set_baseband_bandwidth(2500000);
|
||||||
|
@ -22,14 +22,11 @@
|
|||||||
|
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
//#include "ui_textentry.hpp"
|
#include "ui_receiver.hpp"
|
||||||
//#include "ui_widget.hpp"
|
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "ui_font_fixed_8x16.hpp"
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "portapack.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@ -38,9 +35,12 @@ struct ADSBRecentEntry {
|
|||||||
|
|
||||||
static constexpr Key invalid_key = 0xffffffff;
|
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
|
uint8_t raw_data[14] { }; // 112 bits at most
|
||||||
|
std::string callsign { " " };
|
||||||
std::string time { "" };
|
std::string time { "" };
|
||||||
|
std::string geo_pos { "" };
|
||||||
|
|
||||||
ADSBRecentEntry(
|
ADSBRecentEntry(
|
||||||
) : ADSBRecentEntry { 0 }
|
) : ADSBRecentEntry { 0 }
|
||||||
@ -57,6 +57,18 @@ struct ADSBRecentEntry {
|
|||||||
return ICAO_address;
|
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) {
|
void set_time(std::string& new_time) {
|
||||||
time = new_time;
|
time = new_time;
|
||||||
}
|
}
|
||||||
@ -75,34 +87,34 @@ public:
|
|||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
std::string title() const override { return "ADS-B debug"; };
|
std::string title() const override { return "ADS-B receive"; };
|
||||||
|
|
||||||
private:
|
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);
|
void on_frame(const ADSBFrameMessage * message);
|
||||||
|
|
||||||
const RecentEntriesColumns columns { {
|
const RecentEntriesColumns columns { {
|
||||||
{ "Raw", 21 },
|
{ "ICAO", 6 },
|
||||||
{ "Time", 8 },
|
{ "Callsign", 8 },
|
||||||
|
{ "Hits", 5 },
|
||||||
|
{ "Time", 8 }
|
||||||
} };
|
} };
|
||||||
ADSBRecentEntries recent { };
|
ADSBRecentEntries recent { };
|
||||||
RecentEntriesView<RecentEntries<ADSBRecentEntry>> recent_entries_view { columns, recent };
|
RecentEntriesView<RecentEntries<ADSBRecentEntry>> recent_entries_view { columns, recent };
|
||||||
|
|
||||||
/*Labels labels {
|
RSSI rssi {
|
||||||
{ { 0 * 8, 0 * 8 }, "Test", Color::light_grey() }
|
{ 19 * 8, 4, 10 * 8, 8 },
|
||||||
};*/
|
};
|
||||||
|
|
||||||
NumberField offset_field {
|
LNAGainField field_lna {
|
||||||
{ 0, 0 },
|
{ 4 * 8, 0 * 16 }
|
||||||
6,
|
};
|
||||||
{ 0, 819200 }, // * 256 -> file offset
|
|
||||||
1,
|
VGAGainField field_vga {
|
||||||
'0'
|
{ 11 * 8, 0 * 16 }
|
||||||
|
};
|
||||||
|
|
||||||
|
Labels labels {
|
||||||
|
{ { 0 * 8, 0 * 8 }, "LNA: VGA: RSSI:", Color::light_grey() }
|
||||||
};
|
};
|
||||||
|
|
||||||
Text text_debug_a {
|
Text text_debug_a {
|
||||||
@ -118,11 +130,6 @@ private:
|
|||||||
"-"
|
"-"
|
||||||
};
|
};
|
||||||
|
|
||||||
/*Button button_ffw {
|
|
||||||
{ 184, 0 * 16, 56, 16 },
|
|
||||||
"FFW"
|
|
||||||
};*/
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_frame {
|
MessageHandlerRegistration message_handler_frame {
|
||||||
Message::ID::ADSBFrame,
|
Message::ID::ADSBFrame,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
|
@ -73,28 +73,10 @@ void Compass::paint(Painter&) {
|
|||||||
set_value(value_);
|
set_value(value_);
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBView::ADSBView() {
|
ADSBPositionView::ADSBPositionView(
|
||||||
add_child(&check_enable);
|
NavigationView& nav, Rect parent_rect
|
||||||
hidden(true);
|
) : OptionTabView(parent_rect)
|
||||||
|
{
|
||||||
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) {
|
|
||||||
set_type("position");
|
set_type("position");
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
@ -133,7 +115,10 @@ void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector<A
|
|||||||
frame_list.emplace_back(temp_frame);
|
frame_list.emplace_back(temp_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBCallsignView::ADSBCallsignView(NavigationView& nav) {
|
ADSBCallsignView::ADSBCallsignView(
|
||||||
|
NavigationView& nav, Rect parent_rect
|
||||||
|
) : OptionTabView(parent_rect)
|
||||||
|
{
|
||||||
set_type("callsign");
|
set_type("callsign");
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
@ -146,7 +131,14 @@ ADSBCallsignView::ADSBCallsignView(NavigationView& nav) {
|
|||||||
button_callsign.set_text(callsign);
|
button_callsign.set_text(callsign);
|
||||||
|
|
||||||
button_callsign.on_select = [this, &nav](Button&) {
|
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);
|
frame_list.emplace_back(temp_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBSpeedView::ADSBSpeedView() {
|
ADSBSpeedView::ADSBSpeedView(
|
||||||
|
Rect parent_rect
|
||||||
|
) : OptionTabView(parent_rect)
|
||||||
|
{
|
||||||
set_type("speed");
|
set_type("speed");
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
@ -189,7 +184,10 @@ void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector<ADSB
|
|||||||
frame_list.emplace_back(temp_frame);
|
frame_list.emplace_back(temp_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBSquawkView::ADSBSquawkView() {
|
ADSBSquawkView::ADSBSquawkView(
|
||||||
|
Rect parent_rect
|
||||||
|
) : OptionTabView(parent_rect)
|
||||||
|
{
|
||||||
set_type("squawk");
|
set_type("squawk");
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
@ -325,7 +323,6 @@ ADSBTxView::ADSBTxView(
|
|||||||
NavigationView& nav
|
NavigationView& nav
|
||||||
) : nav_ { nav }
|
) : nav_ { nav }
|
||||||
{
|
{
|
||||||
Rect view_rect = { 0, 7 * 8, 240, 192 };
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_adsb_tx);
|
baseband::run_image(portapack::spi_flash::image_tag_adsb_tx);
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
@ -340,11 +337,6 @@ ADSBTxView::ADSBTxView(
|
|||||||
&tx_view
|
&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]() {
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||||
new_view->on_changed = [this](rf::Frequency f) {
|
new_view->on_changed = [this](rf::Frequency f) {
|
||||||
|
@ -22,14 +22,10 @@
|
|||||||
|
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "adsb.hpp"
|
#include "adsb.hpp"
|
||||||
#include "utility.hpp"
|
|
||||||
#include "ui_textentry.hpp"
|
#include "ui_textentry.hpp"
|
||||||
#include "ui_widget.hpp"
|
|
||||||
#include "ui_geomap.hpp"
|
#include "ui_geomap.hpp"
|
||||||
#include "ui_tabview.hpp"
|
#include "ui_tabview.hpp"
|
||||||
#include "ui_navigation.hpp"
|
|
||||||
#include "ui_transmitter.hpp"
|
#include "ui_transmitter.hpp"
|
||||||
|
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "transmitter_model.hpp"
|
#include "transmitter_model.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
@ -51,32 +47,9 @@ private:
|
|||||||
uint32_t value_ { 0 };
|
uint32_t value_ { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
class ADSBView : public View {
|
class ADSBPositionView : public OptionTabView {
|
||||||
public:
|
public:
|
||||||
ADSBView();
|
ADSBPositionView(NavigationView& nav, Rect parent_rect);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
|
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:
|
public:
|
||||||
ADSBCallsignView(NavigationView& nav);
|
ADSBCallsignView(NavigationView& nav, Rect parent_rect);
|
||||||
|
|
||||||
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
|
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:
|
public:
|
||||||
ADSBSpeedView();
|
ADSBSpeedView(Rect parent_rect);
|
||||||
|
|
||||||
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
|
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:
|
public:
|
||||||
ADSBSquawkView();
|
ADSBSquawkView(Rect parent_rect);
|
||||||
|
|
||||||
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
|
void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
|
||||||
|
|
||||||
@ -223,10 +196,12 @@ private:
|
|||||||
void start_tx();
|
void start_tx();
|
||||||
void generate_frames();
|
void generate_frames();
|
||||||
|
|
||||||
ADSBPositionView view_position { nav_ };
|
Rect view_rect = { 0, 7 * 8, 240, 192 };
|
||||||
ADSBCallsignView view_callsign { nav_ };
|
|
||||||
ADSBSpeedView view_speed { };
|
ADSBPositionView view_position { nav_, view_rect };
|
||||||
ADSBSquawkView view_squawk { };
|
ADSBCallsignView view_callsign { nav_, view_rect };
|
||||||
|
ADSBSpeedView view_speed { view_rect };
|
||||||
|
ADSBSquawkView view_squawk { view_rect };
|
||||||
|
|
||||||
TabView tab_view {
|
TabView tab_view {
|
||||||
{ "Position", Color::cyan(), &view_position },
|
{ "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_sampling_rate(2280000U);
|
||||||
transmitter_model.set_rf_amp(true);
|
transmitter_model.set_rf_amp(true);
|
||||||
transmitter_model.set_lna(40);
|
|
||||||
transmitter_model.set_vga(40);
|
|
||||||
transmitter_model.set_baseband_bandwidth(1750000);
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ void NavigationView::focus() {
|
|||||||
|
|
||||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||||
add_items({
|
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>(); } },
|
{ "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } },
|
||||||
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } },
|
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||||
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(false); } },
|
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(false); } },
|
||||||
@ -301,7 +301,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
|||||||
|
|
||||||
TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||||
add_items({
|
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>(); } },
|
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<APRSTXView>(); } },
|
||||||
{ "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push<BHTView>(); } },
|
{ "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push<BHTView>(); } },
|
||||||
{ "Jammer", ui::Color::yellow(), &bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
{ "Jammer", ui::Color::yellow(), &bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
||||||
|
@ -33,12 +33,145 @@ using namespace rds;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
void RDSView::focus() {
|
RDSPSNView::RDSPSNView(
|
||||||
button_editpsn.focus();
|
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) {
|
RDSRadioTextView::RDSRadioTextView(
|
||||||
transmitter_model.set_tuning_frequency(f);
|
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() {
|
RDSView::~RDSView() {
|
||||||
@ -47,80 +180,58 @@ RDSView::~RDSView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RDSView::start_tx() {
|
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_sampling_rate(2280000U);
|
||||||
transmitter_model.set_rf_amp(true);
|
transmitter_model.set_rf_amp(true);
|
||||||
transmitter_model.set_baseband_bandwidth(1750000);
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
baseband::set_rds_data(message_length);
|
tx_thread = std::make_unique<RDSThread>(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RDSView::paint(Painter&) {
|
RDSView::RDSView(
|
||||||
text_psn.set(PSN);
|
NavigationView& nav
|
||||||
text_radiotexta.set(RadioText.substr(0, 16));
|
) : nav_ { nav }
|
||||||
text_radiotextb.set(RadioText.substr(16, 16));
|
{
|
||||||
}
|
|
||||||
|
|
||||||
RDSView::RDSView(NavigationView& nav) {
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_rds);
|
baseband::run_image(portapack::spi_flash::image_tag_rds);
|
||||||
|
|
||||||
PSN = "TEST1234";
|
|
||||||
RadioText = "Radiotext test ABCD1234";
|
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
|
&tab_view,
|
||||||
&labels,
|
&labels,
|
||||||
&options_pty,
|
|
||||||
&options_countrycode,
|
|
||||||
&options_coverage,
|
|
||||||
&options_tx,
|
|
||||||
&check_mono_stereo,
|
|
||||||
&check_TA,
|
|
||||||
&check_TP,
|
|
||||||
&check_MS,
|
|
||||||
&sym_pi_code,
|
&sym_pi_code,
|
||||||
&button_editpsn,
|
&check_TP,
|
||||||
&text_psn,
|
&options_pty,
|
||||||
&button_editradiotext,
|
&view_PSN,
|
||||||
&text_radiotexta,
|
&view_radiotext,
|
||||||
&text_radiotextb,
|
&view_datetime,
|
||||||
|
&view_audio,
|
||||||
|
//&options_countrycode,
|
||||||
|
//&options_coverage,
|
||||||
&tx_view,
|
&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);
|
check_TP.set_value(true);
|
||||||
|
|
||||||
sym_pi_code.set_sym(0, 0xF);
|
sym_pi_code.set_sym(0, 0xF);
|
||||||
@ -132,15 +243,26 @@ RDSView::RDSView(NavigationView& nav) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
options_pty.set_selected_index(0); // None
|
options_pty.set_selected_index(0); // None
|
||||||
options_countrycode.set_selected_index(18); // Baguette du fromage
|
//options_countrycode.set_selected_index(18); // Baguette du fromage
|
||||||
options_coverage.set_selected_index(0); // Local
|
//options_coverage.set_selected_index(0); // Local
|
||||||
|
|
||||||
button_editpsn.on_select = [this, &nav](Button&) {
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
text_prompt(nav, &PSN, 8);
|
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&){
|
tx_view.on_start = [this]() {
|
||||||
text_prompt(nav, &RadioText, 24);
|
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.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_transmitter.hpp"
|
||||||
#include "ui_textentry.hpp"
|
#include "ui_textentry.hpp"
|
||||||
#include "message.hpp"
|
#include "ui_tabview.hpp"
|
||||||
|
|
||||||
#include "rds.hpp"
|
#include "rds.hpp"
|
||||||
|
|
||||||
using namespace rds;
|
using namespace rds;
|
||||||
|
|
||||||
namespace ui {
|
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 {
|
class RDSView : public View {
|
||||||
public:
|
public:
|
||||||
RDSView(NavigationView& nav);
|
RDSView(NavigationView& nav);
|
||||||
~RDSView();
|
~RDSView();
|
||||||
|
|
||||||
|
RDSView(const RDSView&) = delete;
|
||||||
|
RDSView(RDSView&&) = delete;
|
||||||
|
RDSView& operator=(const RDSView&) = delete;
|
||||||
|
RDSView& operator=(RDSView&&) = delete;
|
||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
void paint(Painter& painter) override;
|
|
||||||
|
|
||||||
std::string title() const override { return "RDS transmit"; };
|
std::string title() const override { return "RDS transmit"; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string PSN { 0 };
|
NavigationView& nav_;
|
||||||
std::string RadioText { 0 };
|
|
||||||
bool txing = false;
|
|
||||||
RDS_flags rds_flags { };
|
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 };
|
uint16_t message_length { 0 };
|
||||||
|
|
||||||
void start_tx();
|
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 {
|
Labels labels {
|
||||||
{ { 1 * 8, 16 + 8 }, "PTY:", Color::light_grey() },
|
{ { 0 * 8, 28 }, "Program type:", Color::light_grey() },
|
||||||
{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
|
//{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
|
||||||
{ { 1 * 8, 32 + 8 }, "PI:", Color::light_grey() },
|
{ { 2 * 8, 28 + 16 }, "Program ID:", Color::light_grey() },
|
||||||
{ { 13 * 8, 32 + 8 }, "Cov:", 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() }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField options_pty {
|
OptionsField options_pty {
|
||||||
{ 5 * 8, 16 + 8 },
|
{ 13 * 8, 28 },
|
||||||
8,
|
8,
|
||||||
{
|
{
|
||||||
{ "None", 0 },
|
{ "None", 0 },
|
||||||
@ -104,7 +221,7 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField options_countrycode {
|
/*OptionsField options_countrycode {
|
||||||
{ 17 * 8, 16 + 8 },
|
{ 17 * 8, 16 + 8 },
|
||||||
11,
|
11,
|
||||||
{
|
{
|
||||||
@ -171,15 +288,15 @@ private:
|
|||||||
{ "Vatican", 4 },
|
{ "Vatican", 4 },
|
||||||
{ "Yugoslavia", 13 }
|
{ "Yugoslavia", 13 }
|
||||||
}
|
}
|
||||||
};
|
};*/
|
||||||
|
|
||||||
SymField sym_pi_code {
|
SymField sym_pi_code {
|
||||||
{ 4 * 8, 32 + 8 },
|
{ 13 * 8, 28 + 16 },
|
||||||
4,
|
4,
|
||||||
SymField::SYMFIELD_HEX
|
SymField::SYMFIELD_HEX
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField options_coverage {
|
/*OptionsField options_coverage {
|
||||||
{ 17 * 8, 32 + 8 },
|
{ 17 * 8, 32 + 8 },
|
||||||
12,
|
12,
|
||||||
{
|
{
|
||||||
@ -200,66 +317,21 @@ private:
|
|||||||
{ "R111", 14 },
|
{ "R111", 14 },
|
||||||
{ "R112", 15 }
|
{ "R112", 15 }
|
||||||
}
|
}
|
||||||
};
|
};*/
|
||||||
|
|
||||||
Checkbox check_mono_stereo {
|
|
||||||
{ 1 * 8, 4 * 16 },
|
|
||||||
6,
|
|
||||||
"Stereo"
|
|
||||||
};
|
|
||||||
Checkbox check_TA {
|
|
||||||
{ 12 * 8, 4 * 16 },
|
|
||||||
2,
|
|
||||||
"TA"
|
|
||||||
};
|
|
||||||
Checkbox check_TP {
|
Checkbox check_TP {
|
||||||
{ 18 * 8, 4 * 16 },
|
{ 23 * 8, 4 * 8 },
|
||||||
2,
|
2,
|
||||||
"TP"
|
"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 {
|
TransmitterView tx_view {
|
||||||
16 * 16,
|
16 * 16,
|
||||||
50000,
|
50000,
|
||||||
9
|
9
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<RDSThread> tx_thread { };
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -141,7 +141,7 @@ TransmitterView::TransmitterView(
|
|||||||
on_edit_frequency();
|
on_edit_frequency();
|
||||||
};
|
};
|
||||||
field_frequency.on_change = [this](rf::Frequency f) {
|
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&){
|
button_start.on_select = [this](Button&){
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "baseband_processor.hpp"
|
#include "baseband_processor.hpp"
|
||||||
#include "baseband_thread.hpp"
|
#include "baseband_thread.hpp"
|
||||||
|
#include "rssi_thread.hpp"
|
||||||
|
|
||||||
#include "adsb_frame.hpp"
|
#include "adsb_frame.hpp"
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ private:
|
|||||||
static constexpr size_t baseband_fs = 2000000;
|
static constexpr size_t baseband_fs = 2000000;
|
||||||
|
|
||||||
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
|
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
|
||||||
|
RSSIThread rssi_thread { NORMALPRIO + 10 };
|
||||||
|
|
||||||
ADSBFrame frame { };
|
ADSBFrame frame { };
|
||||||
bool configured { false };
|
bool configured { false };
|
||||||
|
@ -70,6 +70,27 @@ void encode_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::s
|
|||||||
frame.make_CRC();
|
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) {
|
/*void generate_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code) {
|
||||||
make_frame_mode_s(frame, ICAO_address);
|
make_frame_mode_s(frame, ICAO_address);
|
||||||
|
|
||||||
@ -141,26 +162,6 @@ int cpr_N(float lat, int is_odd) {
|
|||||||
return nl;
|
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,
|
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) {
|
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();
|
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
|
// speed is in knots
|
||||||
// vertical rate is in ft/min
|
// vertical rate is in ft/min
|
||||||
void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed,
|
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 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);
|
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,
|
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);
|
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,
|
void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed,
|
||||||
const float angle, const int32_t v_rate);
|
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_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code);
|
||||||
|
|
||||||
void encode_frame_squawk(ADSBFrame& frame, const uint32_t squawk);
|
void encode_frame_squawk(ADSBFrame& frame, const uint32_t squawk);
|
||||||
|
|
||||||
} /* namespace adsb */
|
} /* namespace adsb */
|
||||||
|
@ -61,38 +61,40 @@ public:
|
|||||||
return 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() {
|
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;
|
uint8_t b, c, s, bitn;
|
||||||
const uint32_t crc_poly = 0x1205FFF;
|
const uint32_t crc_poly = 0x1205FFF;
|
||||||
|
|
||||||
// Clear CRC
|
// Copy frame data
|
||||||
raw_data[11] = 0x00;
|
memcpy(adsb_crc, raw_data, 11);
|
||||||
raw_data[12] = 0x00;
|
|
||||||
raw_data[13] = 0x00;
|
|
||||||
|
|
||||||
// Compute CRC
|
// Compute CRC
|
||||||
memcpy(adsb_crc, raw_data, 14);
|
|
||||||
for (c = 0; c < 11; c++) {
|
for (c = 0; c < 11; c++) {
|
||||||
for (b = 0; b < 8; b++) {
|
for (b = 0; b < 8; b++) {
|
||||||
if ((adsb_crc[c] << b) & 0x80) {
|
if ((adsb_crc[c] << b) & 0x80) {
|
||||||
@ -104,15 +106,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert CRC in frame
|
return (adsb_crc[11] << 16) + (adsb_crc[12] << 8) + adsb_crc[13];
|
||||||
memcpy(&raw_data[11], &adsb_crc[11], 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
} /* namespace adsb */
|
||||||
|
@ -276,6 +276,35 @@ std::string View::title() const {
|
|||||||
return "";
|
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::Rectangle(
|
Rectangle::Rectangle(
|
||||||
|
@ -603,6 +603,29 @@ private:
|
|||||||
bool show_max_;
|
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 */
|
} /* namespace ui */
|
||||||
|
|
||||||
#endif/*__UI_WIDGET_H__*/
|
#endif/*__UI_WIDGET_H__*/
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user