mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-10 08:39:29 +00:00
Merge branch 'next' of https://github.com/Brumi-2021/portapack-mayhem into next
This commit is contained in:
@@ -172,6 +172,7 @@ set(CPPSRC
|
||||
irq_rtc.cpp
|
||||
log_file.cpp
|
||||
portapack.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
receiver_model.cpp
|
||||
recent_entries.cpp
|
||||
@@ -206,6 +207,7 @@ set(CPPSRC
|
||||
ui/ui_channel.cpp
|
||||
ui/ui_font_fixed_8x16.cpp
|
||||
ui/ui_geomap.cpp
|
||||
ui/ui_qrcode.cpp
|
||||
ui/ui_menu.cpp
|
||||
ui/ui_btngrid.cpp
|
||||
ui/ui_receiver.cpp
|
||||
|
||||
@@ -59,7 +59,7 @@ static float latlon_float(const int32_t normalized) {
|
||||
static std::string mmsi(
|
||||
const ais::MMSI& mmsi
|
||||
) {
|
||||
return to_string_dec_uint(mmsi, 9);
|
||||
return to_string_dec_uint(mmsi, 9, '0'); // MMSI is always is always 9 characters pre-padded with zeros
|
||||
}
|
||||
|
||||
static std::string navigational_status(const unsigned int value) {
|
||||
@@ -240,7 +240,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||
|
||||
void AISRecentEntryDetailView::update_position() {
|
||||
if (send_updates)
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading);
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0);
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
|
||||
@@ -75,8 +75,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
|
||||
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 khz (BW) and fs 8 Mhz , when selected 1 Mhz BW ...*/
|
||||
/* ex. recording 500khz BW to .C16 file, base_rate clock 500khz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz , when selected 1 Mhz BW ...*/
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
|
||||
waterfall.on_hide();
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
@@ -85,12 +85,12 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
|
||||
switch(sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250Khz max ) fs = 8 x 250 Khz
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750Khz max ) fs_max = 8 x 750Khz = 6Mhz
|
||||
// BW 500k ... 750khz , ex. 500khz (fs = 8*BW = 4Mhz) , BW 600Khz (fs = 4,8Mhz) , BW 750 Khz (fs = 6Mhz)
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
|
||||
@@ -123,7 +123,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
waterfall.on_show();
|
||||
};
|
||||
|
||||
option_bandwidth.set_selected_index(7); // 500k, Preselected starting default option 500khz
|
||||
option_bandwidth.set_selected_index(7); // 500k, Preselected starting default option 500kHz
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
receiver_model.enable();
|
||||
|
||||
@@ -145,16 +145,23 @@ void ReplayAppView::start() {
|
||||
field_rfgain.set_value(tx_gain);
|
||||
receiver_model.set_tx_gain(tx_gain);
|
||||
|
||||
|
||||
field_rfamp.on_change = [this](int32_t v) {
|
||||
rf_amp = (bool)v;
|
||||
};
|
||||
field_rfamp.set_value(rf_amp ? 14 : 0);
|
||||
|
||||
radio::enable({
|
||||
receiver_model.tuning_frequency(),
|
||||
sample_rate * 8 ,
|
||||
baseband_bandwidth,
|
||||
rf::Direction::Transmit,
|
||||
receiver_model.rf_amp(),
|
||||
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
|
||||
static_cast<int8_t>(receiver_model.lna()),
|
||||
static_cast<int8_t>(receiver_model.vga())
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void ReplayAppView::stop(const bool do_loop) {
|
||||
if( is_active() )
|
||||
@@ -186,7 +193,9 @@ ReplayAppView::ReplayAppView(
|
||||
) : nav_ (nav)
|
||||
{
|
||||
|
||||
tx_gain = 35;field_rfgain.set_value(tx_gain);
|
||||
tx_gain = 35;field_rfgain.set_value(tx_gain); // Initial default value (-12 dB's max ).
|
||||
field_rfamp.set_value(rf_amp ? 14 : 0); // Initial default value True. (TX RF amp on , +14dB's)
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
add_children({
|
||||
@@ -198,7 +207,7 @@ ReplayAppView::ReplayAppView(
|
||||
&progressbar,
|
||||
&field_frequency,
|
||||
&field_rfgain,
|
||||
&field_rf_amp,
|
||||
&field_rfamp, // let's not use common rf_amp
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&waterfall,
|
||||
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
|
||||
uint32_t sample_rate = 0;
|
||||
int32_t tx_gain { 47 };
|
||||
bool rf_amp { true }; // aux private var to store temporal, Replay App rf_amp user selection.
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
const size_t read_size { 16384 };
|
||||
const size_t buffer_count { 3 };
|
||||
@@ -112,8 +113,12 @@ private:
|
||||
1,
|
||||
' '
|
||||
};
|
||||
RFAmpField field_rf_amp {
|
||||
{ 19 * 8, 2 * 16 }
|
||||
NumberField field_rfamp { // previously I was using "RFAmpField field_rf_amp" but that is general Receiver amp setting.
|
||||
{ 19 * 8, 2 * 16 },
|
||||
2,
|
||||
{ 0, 14 }, // this time we will display GUI , 0 or 14 dBs same as Mic App
|
||||
14,
|
||||
' '
|
||||
};
|
||||
Checkbox check_loop {
|
||||
{ 21 * 8, 2 * 16 },
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace ui
|
||||
{
|
||||
add_children({&console, &button_ok});
|
||||
|
||||
button_ok.on_select = [&nav](Button &) {
|
||||
button_ok.on_select = [&nav](Button &)
|
||||
{
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -34,14 +35,16 @@ namespace ui
|
||||
console.writeln("zhang00963,RedFox-Fr,aldude999");
|
||||
console.writeln("East2West,fossum,ArjanOnwezen");
|
||||
console.writeln("vXxOinvizioNxX,teixeluis");
|
||||
console.writeln("heurist1,intoxsick");
|
||||
console.writeln("Brumi-2021,texasyojimbo");
|
||||
console.writeln("heurist1,intoxsick,ckuethe");
|
||||
console.writeln("notpike");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
|
||||
console.writeln("\x1B\x06Havoc:\x1B\x10");
|
||||
console.writeln("furrtek,mrmookie,notpike");
|
||||
console.writeln("furrtek,mrmookie,NotPike");
|
||||
console.writeln("mjwaxios,ImDroided,Giorgiofox");
|
||||
console.writeln("F4GEV,z4ziggy,xmycroftx");
|
||||
console.writeln("troussos,silascutler");
|
||||
|
||||
@@ -72,7 +72,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
||||
to_string_dec_uint((unsigned int)entry.velo.speed,4) +
|
||||
to_string_dec_uint((unsigned int)(entry.amp>>9),4) + " " +
|
||||
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
|
||||
to_string_dec_uint(entry.age, 3);
|
||||
to_string_dec_uint(entry.age, 4);
|
||||
#endif
|
||||
|
||||
painter.draw_string(
|
||||
@@ -91,6 +91,153 @@ void ADSBLogger::log_str(std::string& logline) {
|
||||
log_file.write_entry(datetime,logline);
|
||||
}
|
||||
|
||||
|
||||
// Aircraft Details
|
||||
void ADSBRxAircraftDetailsView::focus() {
|
||||
button_close.focus();
|
||||
}
|
||||
|
||||
ADSBRxAircraftDetailsView::~ADSBRxAircraftDetailsView() {
|
||||
on_close_();
|
||||
}
|
||||
|
||||
ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
NavigationView& nav,
|
||||
const AircraftRecentEntry& entry,
|
||||
const std::function<void(void)> on_close
|
||||
) : entry_copy(entry),
|
||||
on_close_(on_close)
|
||||
{
|
||||
char file_buffer[32] { 0 };
|
||||
bool found = false;
|
||||
size_t number_of_icao_codes = 0;
|
||||
std::string icao_code;
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&text_icao_address,
|
||||
&text_registration,
|
||||
&text_manufacturer,
|
||||
&text_model,
|
||||
&text_type,
|
||||
&text_number_of_engines,
|
||||
&text_engine_type,
|
||||
&text_owner,
|
||||
&text_operator,
|
||||
&button_close
|
||||
});
|
||||
|
||||
std::unique_ptr<ADSBLogger> logger { };
|
||||
//update(entry_copy);
|
||||
|
||||
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
|
||||
|
||||
|
||||
// Try getting the aircraft information from icao24.db
|
||||
auto result = db_file.open("ADSB/icao24.db");
|
||||
if (!result.is_valid()) {
|
||||
// determine number of ICAO24 codes in file, total size / (single index + record size)
|
||||
number_of_icao_codes = (db_file.size() / 153);
|
||||
icao_code = to_string_hex(entry_copy.ICAO_address, 6);
|
||||
|
||||
// binary search
|
||||
int first = 0, // First search element
|
||||
last = number_of_icao_codes - 1, // Last search element
|
||||
middle, // Mid point of search
|
||||
position = -1; // Position of search value
|
||||
while (!found && first <= last) {
|
||||
middle = (first + last) / 2; // Calculate mid point
|
||||
db_file.seek(middle * 7);
|
||||
db_file.read(file_buffer, 6);
|
||||
if (file_buffer == icao_code) { // If value is found at mid
|
||||
found = true;
|
||||
position = middle;
|
||||
}
|
||||
else if (file_buffer > icao_code) // If value is in lower half
|
||||
last = middle - 1;
|
||||
else
|
||||
first = middle + 1; // If value is in upper half
|
||||
}
|
||||
|
||||
if (position > -1) {
|
||||
db_file.seek((number_of_icao_codes * 7) + (position * 146)); // seek starting after index
|
||||
db_file.read(file_buffer, 9);
|
||||
text_registration.set(file_buffer);
|
||||
db_file.read(file_buffer, 33);
|
||||
text_manufacturer.set(file_buffer);
|
||||
db_file.read(file_buffer, 33);
|
||||
text_model.set(file_buffer);
|
||||
db_file.read(file_buffer, 5); // ICAO type decripton
|
||||
if(strlen(file_buffer) == 3) {
|
||||
switch(file_buffer[0]) {
|
||||
case 'L':
|
||||
text_type.set("Landplane");
|
||||
break;
|
||||
case 'S':
|
||||
text_type.set("Seaplane");
|
||||
break;
|
||||
case 'A':
|
||||
text_type.set("Amphibian");
|
||||
break;
|
||||
case 'H':
|
||||
text_type.set("Helicopter");
|
||||
break;
|
||||
case 'G':
|
||||
text_type.set("Gyrocopter");
|
||||
break;
|
||||
case 'T':
|
||||
text_type.set("Tilt-wing aircraft");
|
||||
break;
|
||||
}
|
||||
text_number_of_engines.set(std::string(1, file_buffer[1]));
|
||||
switch(file_buffer[2]) {
|
||||
case 'P':
|
||||
text_engine_type.set("Piston engine");
|
||||
break;
|
||||
case 'T':
|
||||
text_engine_type.set("Turboprop/Turboshaft engine");
|
||||
break;
|
||||
case 'J':
|
||||
text_engine_type.set("Jet engine");
|
||||
break;
|
||||
case 'E':
|
||||
text_engine_type.set("Electric engine");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
// check for ICAO type designator
|
||||
else if(strlen(file_buffer) == 4) {
|
||||
if(strcmp(file_buffer,"SHIP") == 0) text_type.set("Airship");
|
||||
else if(strcmp(file_buffer,"BALL") == 0) text_type.set("Balloon");
|
||||
else if(strcmp(file_buffer,"GLID") == 0) text_type.set("Glider / sailplane");
|
||||
else if(strcmp(file_buffer,"ULAC") == 0) text_type.set("Micro/ultralight aircraft");
|
||||
else if(strcmp(file_buffer,"GYRO") == 0) text_type.set("Micro/ultralight autogyro");
|
||||
else if(strcmp(file_buffer,"UHEL") == 0) text_type.set("Micro/ultralight helicopter");
|
||||
else if(strcmp(file_buffer,"SHIP") == 0) text_type.set("Airship");
|
||||
else if(strcmp(file_buffer,"PARA") == 0) text_type.set("Powered parachute/paraplane");
|
||||
}
|
||||
db_file.read(file_buffer, 33);
|
||||
text_owner.set(file_buffer);
|
||||
db_file.read(file_buffer, 33);
|
||||
text_operator.set(file_buffer);
|
||||
} else {
|
||||
text_registration.set("Unknown");
|
||||
text_manufacturer.set("Unknown");
|
||||
}
|
||||
} else {
|
||||
text_manufacturer.set("No icao24.db file");
|
||||
}
|
||||
|
||||
|
||||
button_close.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// End of Aicraft details
|
||||
|
||||
void ADSBRxDetailsView::focus() {
|
||||
button_see_map.focus();
|
||||
}
|
||||
@@ -114,7 +261,7 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
|
||||
text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
|
||||
|
||||
if (send_updates)
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading);
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading, entry_copy.pos.altitude);
|
||||
}
|
||||
|
||||
ADSBRxDetailsView::~ADSBRxDetailsView() {
|
||||
@@ -130,12 +277,12 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
{
|
||||
char file_buffer[32] { 0 };
|
||||
bool found = false;
|
||||
int number_of_airlines = 0;
|
||||
size_t number_of_airlines = 0;
|
||||
std::string airline_code;
|
||||
size_t c;
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&text_icao_address,
|
||||
&text_callsign,
|
||||
&text_last_seen,
|
||||
&text_airline,
|
||||
@@ -144,6 +291,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
&text_info2,
|
||||
&text_frame_pos_even,
|
||||
&text_frame_pos_odd,
|
||||
&button_aircraft_details,
|
||||
&button_see_map
|
||||
});
|
||||
|
||||
@@ -157,19 +305,28 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
// Search for 3-letter code
|
||||
number_of_airlines = (db_file.size() / 68); // determine number of airlines in file
|
||||
airline_code = entry_copy.callsign.substr(0, 3);
|
||||
c = 0;
|
||||
do {
|
||||
db_file.read(file_buffer, 4);
|
||||
if (!file_buffer[0])
|
||||
break;
|
||||
if (!airline_code.compare(0, 4, file_buffer))
|
||||
found = true;
|
||||
else
|
||||
c++;
|
||||
} while (!found && (c < number_of_airlines));
|
||||
|
||||
// binary search
|
||||
int first = 0, // First search element
|
||||
last = number_of_airlines - 1, // Last search element
|
||||
middle, // Mid point of search
|
||||
position = -1; // Position of search value
|
||||
while (!found && first <= last) {
|
||||
middle = (first + last) / 2; // Calculate mid point
|
||||
db_file.seek(middle * 4);
|
||||
db_file.read(file_buffer, 3);
|
||||
if (file_buffer == airline_code) { // If value is found at mid
|
||||
found = true;
|
||||
position = middle;
|
||||
}
|
||||
else if (file_buffer > airline_code) // If value is in lower half
|
||||
last = middle - 1;
|
||||
else
|
||||
first = middle + 1; // If value is in upper half
|
||||
}
|
||||
|
||||
if (found) {
|
||||
db_file.seek((number_of_airlines * 4) + (c << 6)); // seek starting after index
|
||||
if (position > -1) {
|
||||
db_file.seek((number_of_airlines * 4) + (position << 6)); // seek starting after index
|
||||
db_file.read(file_buffer, 32);
|
||||
text_airline.set(file_buffer);
|
||||
db_file.read(file_buffer, 32);
|
||||
@@ -184,9 +341,20 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
}
|
||||
|
||||
text_callsign.set(entry_copy.callsign);
|
||||
|
||||
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
|
||||
|
||||
button_aircraft_details.on_select = [this, &nav](Button&) {
|
||||
//detailed_entry_key = entry.key();
|
||||
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(
|
||||
entry_copy,
|
||||
[this]() {
|
||||
send_updates = false;
|
||||
});
|
||||
send_updates = false;
|
||||
};
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
if (!send_updates) { // Prevent recursivley launching the map
|
||||
if (!send_updates) { // Prevent recursively launching the map
|
||||
geomap_view = nav.push<GeoMapView>(
|
||||
entry_copy.callsign,
|
||||
entry_copy.pos.altitude,
|
||||
@@ -202,6 +370,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
void ADSBRxView::focus() {
|
||||
field_vga.focus();
|
||||
}
|
||||
|
||||
@@ -159,6 +159,94 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class ADSBRxAircraftDetailsView : public View {
|
||||
public:
|
||||
ADSBRxAircraftDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
|
||||
~ADSBRxAircraftDetailsView();
|
||||
|
||||
ADSBRxAircraftDetailsView(const ADSBRxAircraftDetailsView&) = delete;
|
||||
ADSBRxAircraftDetailsView(ADSBRxAircraftDetailsView&&) = delete;
|
||||
ADSBRxAircraftDetailsView& operator=(const ADSBRxAircraftDetailsView&) = delete;
|
||||
ADSBRxAircraftDetailsView& operator=(ADSBRxAircraftDetailsView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
void update(const AircraftRecentEntry& entry);
|
||||
|
||||
std::string title() const override { return "AC Details"; };
|
||||
|
||||
AircraftRecentEntry get_current_entry() { return entry_copy; }
|
||||
|
||||
private:
|
||||
AircraftRecentEntry entry_copy { 0 };
|
||||
std::function<void(void)> on_close_ { };
|
||||
bool send_updates { false };
|
||||
File db_file { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0 * 8, 1 * 16 }, "ICAO:", Color::light_grey() },
|
||||
{ { 0 * 8, 2 * 16 }, "Registration:", Color::light_grey() },
|
||||
{ { 0 * 8, 3 * 16 }, "Manufacturer:", Color::light_grey() },
|
||||
{ { 0 * 8, 5 * 16 }, "Model:", Color::light_grey() },
|
||||
{ { 0 * 8, 7 * 16 }, "Type:", Color::light_grey() },
|
||||
{ { 0 * 8, 8 * 16 }, "Number of engines:", Color::light_grey() },
|
||||
{ { 0 * 8, 9 * 16 }, "Engine type:", Color::light_grey() },
|
||||
{ { 0 * 8, 11 * 16 }, "Owner:", Color::light_grey() },
|
||||
{ { 0 * 8, 13 * 16 }, "Operator:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_icao_address {
|
||||
{ 5 * 8, 1 * 16, 6 * 8, 16},
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_registration {
|
||||
{ 13 * 8, 2 * 16, 8 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_manufacturer {
|
||||
{ 0 * 8, 4 * 16, 19 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_model {
|
||||
{ 0 * 8, 6 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_type {
|
||||
{ 5 * 8, 7 * 16, 22 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_number_of_engines {
|
||||
{ 18 * 8, 8 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_engine_type {
|
||||
{ 0 * 8, 10 * 16, 30 * 8, 16},
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_owner {
|
||||
{ 0 * 8, 12 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_operator {
|
||||
{ 0 * 8, 14 * 16, 30 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Button button_close {
|
||||
{ 9 * 8, 16 * 16, 12 * 8, 3 * 16 },
|
||||
"Back"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class ADSBRxDetailsView : public View {
|
||||
public:
|
||||
ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
|
||||
@@ -181,11 +269,13 @@ private:
|
||||
AircraftRecentEntry entry_copy { 0 };
|
||||
std::function<void(void)> on_close_ { };
|
||||
GeoMapView* geomap_view { nullptr };
|
||||
ADSBRxAircraftDetailsView* aircraft_details_view { nullptr };
|
||||
bool send_updates { false };
|
||||
File db_file { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0 * 8, 1 * 16 }, "Callsign:", Color::light_grey() },
|
||||
{ { 0 * 8, 1 * 16 }, "ICAO:", Color::light_grey() },
|
||||
{ { 13 * 8, 1 * 16 }, "Callsign:", Color::light_grey() },
|
||||
{ { 0 * 8, 2 * 16 }, "Last seen:", Color::light_grey() },
|
||||
{ { 0 * 8, 3 * 16 }, "Airline:", Color::light_grey() },
|
||||
{ { 0 * 8, 5 * 16 }, "Country:", Color::light_grey() },
|
||||
@@ -193,8 +283,13 @@ private:
|
||||
{ { 0 * 8, 15 * 16 }, "Odd position frame:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_icao_address {
|
||||
{ 5 * 8, 1 * 16, 6 * 8, 16},
|
||||
"-"
|
||||
};
|
||||
|
||||
Text text_callsign {
|
||||
{ 9 * 8, 1 * 16, 8 * 8, 16 },
|
||||
{ 22 * 8, 1 * 16, 8 * 8, 16 },
|
||||
"-"
|
||||
};
|
||||
|
||||
@@ -232,11 +327,17 @@ private:
|
||||
"-"
|
||||
};
|
||||
|
||||
Button button_aircraft_details {
|
||||
{ 2 * 8, 9 * 16, 12 * 8, 3 * 16 },
|
||||
"A/C details"
|
||||
};
|
||||
|
||||
Button button_see_map {
|
||||
{ 8 * 8, 9 * 16, 14 * 8, 3 * 16 },
|
||||
{ 16 * 8, 9 * 16, 12 * 8, 3 * 16 },
|
||||
"See on map"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class ADSBRxView : public View {
|
||||
public:
|
||||
@@ -274,7 +375,7 @@ private:
|
||||
{ "Spd", 3 },
|
||||
{ "Amp", 3 },
|
||||
{ "Hit", 3 },
|
||||
{ "Age", 3 }
|
||||
{ "Age", 4 }
|
||||
#endif
|
||||
} };
|
||||
AircraftRecentEntries recent { };
|
||||
|
||||
@@ -351,7 +351,7 @@ void APRSDetailsView::update() {
|
||||
}
|
||||
|
||||
if (send_updates)
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0);
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0);
|
||||
}
|
||||
|
||||
APRSDetailsView::~APRSDetailsView() {
|
||||
|
||||
@@ -242,16 +242,16 @@ private:
|
||||
{ 17 * 8, (26 * 8) + 4 },
|
||||
12,
|
||||
{
|
||||
{ "5Khz (SA AM)", 5000 },
|
||||
{ "9Khz (EU AM)", 9000 },
|
||||
{ "10Khz(US AM)", 10000 },
|
||||
{ "50Khz (FM1)", 50000 },
|
||||
{ "100Khz(FM2)", 100000 },
|
||||
{ "6.25khz(NFM)", 6250 },
|
||||
{ "12.5khz(NFM)", 12500 },
|
||||
{ "25khz (N1)", 25000 },
|
||||
{ "250khz (N2)", 250000 },
|
||||
{ "8.33khz(AIR)", 8330 }
|
||||
{ "5kHz (SA AM)", 5000 },
|
||||
{ "9kHz (EU AM)", 9000 },
|
||||
{ "10kHz(US AM)", 10000 },
|
||||
{ "50kHz (FM1)", 50000 },
|
||||
{ "100kHz(FM2)", 100000 },
|
||||
{ "6.25kHz(NFM)", 6250 },
|
||||
{ "12.5kHz(NFM)", 12500 },
|
||||
{ "25kHz (N1)", 25000 },
|
||||
{ "250kHz (N2)", 250000 },
|
||||
{ "8.33kHz(AIR)", 8330 }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -308,4 +308,4 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -41,6 +41,7 @@ void SondeLogger::on_packet(const sonde::Packet& packet) {
|
||||
|
||||
namespace ui {
|
||||
|
||||
|
||||
SondeView::SondeView(NavigationView& nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_sonde);
|
||||
|
||||
@@ -63,6 +64,7 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
&text_temp,
|
||||
&text_humid,
|
||||
&geopos,
|
||||
&button_see_qr,
|
||||
&button_see_map
|
||||
});
|
||||
|
||||
@@ -108,6 +110,12 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
static_cast<int8_t>(receiver_model.vga()),
|
||||
});
|
||||
|
||||
|
||||
// QR code with geo URI
|
||||
button_see_qr.on_select = [this, &nav](Button&) {
|
||||
nav.push<QRCodeView>(geo_uri);
|
||||
};
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
nav.push<GeoMapView>(
|
||||
sonde_id,
|
||||
@@ -154,10 +162,56 @@ void SondeView::focus() {
|
||||
field_vga.focus();
|
||||
}
|
||||
|
||||
|
||||
// used to convert float to character pointer, since unfortunately function like
|
||||
// sprintf and c_str aren't supported.
|
||||
char * SondeView::float_to_char(float x, char *p)
|
||||
{
|
||||
|
||||
char *s = p + 9; // go to end of buffer
|
||||
uint16_t decimals; // variable to store the decimals
|
||||
int units; // variable to store the units (part to left of decimal place)
|
||||
if (x < 0) { // take care of negative numbers
|
||||
decimals = (int)(x * -100000) % 100000; // make 1000 for 3 decimals etc.
|
||||
units = (int)(-1 * x);
|
||||
} else { // positive numbers
|
||||
decimals = (int)(x * 100000) % 100000;
|
||||
units = (int)x;
|
||||
}
|
||||
|
||||
// TODO: more elegant solution (loop?)
|
||||
*--s = (decimals % 10) + '0';
|
||||
decimals /= 10;
|
||||
*--s = (decimals % 10) + '0';
|
||||
decimals /= 10;
|
||||
*--s = (decimals % 10) + '0';
|
||||
decimals /= 10;
|
||||
*--s = (decimals % 10) + '0';
|
||||
decimals /= 10;
|
||||
*--s = (decimals % 10) + '0';
|
||||
*--s = '.';
|
||||
|
||||
while (units > 0) {
|
||||
*--s = (units % 10) + '0';
|
||||
units /= 10;
|
||||
}
|
||||
if (x < 0) *--s = '-'; // unary minus sign for negative numbers
|
||||
return s;
|
||||
}
|
||||
|
||||
void SondeView::on_packet(const sonde::Packet &packet)
|
||||
{
|
||||
if (!use_crc || packet.crc_ok()) //euquiq: Reject bad packet if crc is on
|
||||
{
|
||||
|
||||
char buffer_lat[10] = {};
|
||||
char buffer_lon[10] = {};
|
||||
|
||||
strcpy(geo_uri, "geo:");
|
||||
strcat(geo_uri, float_to_char(gps_info.lat, buffer_lat));
|
||||
strcat(geo_uri, ",");
|
||||
strcat(geo_uri, float_to_char(gps_info.lon, buffer_lon));
|
||||
|
||||
text_signature.set(packet.type_string());
|
||||
|
||||
sonde_id = packet.serial_number(); //used also as tag on the geomap
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_qrcode.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
@@ -63,6 +64,8 @@ public:
|
||||
|
||||
std::string title() const override { return "Radiosonde RX"; };
|
||||
|
||||
|
||||
|
||||
private:
|
||||
std::unique_ptr<SondeLogger> logger { };
|
||||
uint32_t target_frequency_ { 402700000 };
|
||||
@@ -70,6 +73,8 @@ private:
|
||||
bool use_crc { false };
|
||||
bool beep { false };
|
||||
|
||||
char geo_uri[32] = {};
|
||||
|
||||
sonde::GPS_data gps_info { };
|
||||
sonde::temp_humid temp_humid_info { };
|
||||
std::string sonde_id { };
|
||||
@@ -173,9 +178,15 @@ private:
|
||||
{ 0, 12 * 16 },
|
||||
GeoPos::alt_unit::METERS
|
||||
};
|
||||
|
||||
|
||||
|
||||
Button button_see_qr {
|
||||
{ 2 * 8, 15 * 16, 12 * 8, 3 * 16 },
|
||||
"See QR"
|
||||
};
|
||||
|
||||
Button button_see_map {
|
||||
{ 8 * 8, 16 * 16, 14 * 8, 3 * 16 },
|
||||
{ 16 * 8, 15 * 16, 12 * 8, 3 * 16 },
|
||||
"See on map"
|
||||
};
|
||||
|
||||
@@ -190,7 +201,7 @@ private:
|
||||
|
||||
void on_packet(const sonde::Packet& packet);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
|
||||
char * float_to_char(float x, char *p);
|
||||
void set_target_frequency(const uint32_t new_value);
|
||||
|
||||
uint32_t tuning_frequency() const;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2017 NotPike
|
||||
* Copyright (C) 2022 NotPike
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
|
||||
using namespace portapack;
|
||||
using namespace encoders;
|
||||
|
||||
@@ -45,7 +46,13 @@ void TouchTunesView::stop_tx() {
|
||||
transmitter_model.disable();
|
||||
tx_mode = IDLE;
|
||||
progressbar.set_value(0);
|
||||
text_status.set("Ready");
|
||||
|
||||
// EW Mode Check
|
||||
if(check_ew.value()) {
|
||||
start_ew();
|
||||
} else {
|
||||
text_status.set("Ready");
|
||||
}
|
||||
}
|
||||
|
||||
void TouchTunesView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
@@ -72,7 +79,43 @@ void TouchTunesView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
}
|
||||
}
|
||||
|
||||
// EW (Electronic Warfare) Mode will jam the receiving jukebox
|
||||
// while still alowing you (the hacker) to send commands
|
||||
// to the target jukebox.
|
||||
// EW Mode works by transmitting a CW on 433.92MHz inbetween
|
||||
// transmission events.
|
||||
void TouchTunesView::start_ew() {
|
||||
// Radio
|
||||
transmitter_model.set_tuning_frequency(433920000);
|
||||
transmitter_model.set_sampling_rate(3072000U);
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_baseband_bandwidth(3500000U);
|
||||
transmitter_model.set_tx_gain(47);
|
||||
transmitter_model.enable();
|
||||
|
||||
//UI
|
||||
text_status.set("Jamming...");
|
||||
progressbar.set_max(1);
|
||||
progressbar.set_value(1);
|
||||
|
||||
}
|
||||
|
||||
void TouchTunesView::stop_ew() {
|
||||
// Radio
|
||||
transmitter_model.disable();
|
||||
|
||||
// UI
|
||||
text_status.set("Ready");
|
||||
progressbar.set_value(0);
|
||||
}
|
||||
|
||||
void TouchTunesView::start_tx(const uint32_t button_index) {
|
||||
|
||||
// Check EW Mode
|
||||
if(check_ew.value()) {
|
||||
stop_ew();
|
||||
}
|
||||
|
||||
std::string fragments = { "" };
|
||||
size_t bit;
|
||||
uint64_t frame_data;
|
||||
@@ -136,6 +179,7 @@ TouchTunesView::TouchTunesView(
|
||||
&labels,
|
||||
&field_pin,
|
||||
&check_scan,
|
||||
&check_ew,
|
||||
&text_status,
|
||||
&progressbar
|
||||
});
|
||||
@@ -145,6 +189,15 @@ TouchTunesView::TouchTunesView(
|
||||
field_pin.on_change = [this](int32_t v) {
|
||||
pin = v;
|
||||
};
|
||||
|
||||
// EW Mode
|
||||
check_ew.on_select = [this](Checkbox&, bool v) {
|
||||
if(v){
|
||||
start_ew();
|
||||
} else {
|
||||
stop_ew();
|
||||
}
|
||||
};
|
||||
|
||||
const auto button_fn = [this](Button& button) {
|
||||
start_tx(button.id);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2018 NotPike
|
||||
* Copyright (C) 2022 NotPike
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -127,6 +127,8 @@ private:
|
||||
void start_tx(const uint32_t button_index);
|
||||
void stop_tx();
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
void start_ew();
|
||||
void stop_ew();
|
||||
|
||||
struct remote_layout_t {
|
||||
Point position;
|
||||
@@ -191,11 +193,17 @@ private:
|
||||
};
|
||||
|
||||
Checkbox check_scan {
|
||||
{ 2 * 8, 27 * 8 },
|
||||
{ 2 * 8, 25 * 8 },
|
||||
4,
|
||||
"Scan"
|
||||
};
|
||||
|
||||
Checkbox check_ew {
|
||||
{ 2 * 8, 29 * 8 },
|
||||
4,
|
||||
"EW Mode"
|
||||
};
|
||||
|
||||
Text text_status {
|
||||
{ 2 * 8, 33 * 8, 128, 16 },
|
||||
"Ready"
|
||||
|
||||
@@ -51,15 +51,15 @@ enum freqman_entry_type {
|
||||
//Entry step placed for AlainD freqman version (or any other enhanced version)
|
||||
enum freqman_entry_step {
|
||||
STEP_DEF = 0, // default
|
||||
AM_US, // 10 Khz AM/CB
|
||||
AM_EUR, // 9 Khz LW/MW
|
||||
NFM_1, // 12,5 Khz (Analogic PMR 446)
|
||||
NFM_2, // 6,25 Khz (Digital PMR 446)
|
||||
FM_1, // 100 Khz
|
||||
FM_2, // 50 Khz
|
||||
N_1, // 25 Khz
|
||||
N_2, // 250 Khz
|
||||
AIRBAND, // AIRBAND 8,33 Khz
|
||||
AM_US, // 10 kHz AM/CB
|
||||
AM_EUR, // 9 kHz LW/MW
|
||||
NFM_1, // 12,5 kHz (Analogic PMR 446)
|
||||
NFM_2, // 6,25 kHz (Digital PMR 446)
|
||||
FM_1, // 100 kHz
|
||||
FM_2, // 50 kHz
|
||||
N_1, // 25 kHz
|
||||
N_2, // 250 kHz
|
||||
AIRBAND, // AIRBAND 8,33 kHz
|
||||
ERROR_STEP
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma mark - Error Correction Lookup tables
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
|
||||
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium
|
||||
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low
|
||||
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High
|
||||
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile
|
||||
};
|
||||
|
||||
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
|
||||
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
|
||||
// 32, 33, 34, 35, 36, 37, 38, 39, 40
|
||||
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
|
||||
};
|
||||
|
||||
// @TODO: Put other LOCK_VERSIONS here
|
||||
#elif LOCK_VERSION == 3
|
||||
|
||||
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {
|
||||
26, 15, 44, 36
|
||||
};
|
||||
|
||||
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {
|
||||
1, 1, 2, 2
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES = 567;
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported LOCK_VERSION (add it...)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static int max(int a, int b) {
|
||||
if (a > b) { return a; }
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
static int abs(int value) {
|
||||
if (value < 0) { return -value; }
|
||||
return value;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#pragma mark - Mode testing and conversion
|
||||
|
||||
static int8_t getAlphanumeric(char c) {
|
||||
|
||||
if (c >= '0' && c <= '9') { return (c - '0'); }
|
||||
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
|
||||
|
||||
switch (c) {
|
||||
case ' ': return 36;
|
||||
case '$': return 37;
|
||||
case '%': return 38;
|
||||
case '*': return 39;
|
||||
case '+': return 40;
|
||||
case '-': return 41;
|
||||
case '.': return 42;
|
||||
case '/': return 43;
|
||||
case ':': return 44;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool isAlphanumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
if (getAlphanumeric(text[--length]) == -1) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool isNumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
char c = text[--length];
|
||||
if (c < '0' || c > '9') { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Counting
|
||||
|
||||
// We store the following tightly packed (less 8) in modeInfo
|
||||
// <=9 <=26 <= 40
|
||||
// NUMERIC ( 10, 12, 14);
|
||||
// ALPHANUMERIC ( 9, 11, 13);
|
||||
// BYTE ( 8, 16, 16);
|
||||
static char getModeBits(uint8_t version, uint8_t mode) {
|
||||
// Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits
|
||||
// hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2))
|
||||
unsigned int modeInfo = 0x7bbb80a;
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
|
||||
if (version > 9) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
|
||||
if (version > 26) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
|
||||
if (result == 15) { result = 16; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - BitBucket
|
||||
|
||||
typedef struct BitBucket {
|
||||
uint32_t bitOffsetOrWidth;
|
||||
uint16_t capacityBytes;
|
||||
uint8_t *data;
|
||||
} BitBucket;
|
||||
|
||||
/*
|
||||
void bb_dump(BitBucket *bitBuffer) {
|
||||
printf("Buffer: ");
|
||||
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
|
||||
printf("%02x", bitBuffer->data[i]);
|
||||
if ((i % 4) == 3) { printf(" "); }
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
*/
|
||||
|
||||
static uint16_t bb_getGridSizeBytes(uint8_t size) {
|
||||
return (((size * size) + 7) / 8);
|
||||
}
|
||||
|
||||
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
|
||||
return ((bits + 7) / 8);
|
||||
}
|
||||
|
||||
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
|
||||
bitBuffer->bitOffsetOrWidth = 0;
|
||||
bitBuffer->capacityBytes = capacityBytes;
|
||||
bitBuffer->data = data;
|
||||
|
||||
memset(data, 0, bitBuffer->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
|
||||
bitGrid->bitOffsetOrWidth = size;
|
||||
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
|
||||
bitGrid->data = data;
|
||||
|
||||
memset(data, 0, bitGrid->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
|
||||
uint32_t offset = bitBuffer->bitOffsetOrWidth;
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
bitBuffer->bitOffsetOrWidth = offset;
|
||||
}
|
||||
/*
|
||||
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
}
|
||||
*/
|
||||
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
if (on) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
|
||||
if (on ^ invert) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Drawing Patterns
|
||||
|
||||
// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
|
||||
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
|
||||
// This means it is possible to apply a mask, undo it, and try another mask. Note that a final
|
||||
// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.).
|
||||
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
if (bb_getBit(isFunction, x, y)) { continue; }
|
||||
|
||||
bool invert = 0;
|
||||
switch (mask) {
|
||||
case 0: invert = (x + y) % 2 == 0; break;
|
||||
case 1: invert = y % 2 == 0; break;
|
||||
case 2: invert = x % 3 == 0; break;
|
||||
case 3: invert = (x + y) % 3 == 0; break;
|
||||
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||
}
|
||||
bb_invertBit(modules, x, y, invert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
|
||||
bb_setBit(modules, x, y, on);
|
||||
bb_setBit(isFunction, x, y, true);
|
||||
}
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y).
|
||||
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (int8_t i = -4; i <= 4; i++) {
|
||||
for (int8_t j = -4; j <= 4; j++) {
|
||||
uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm
|
||||
int16_t xx = x + j, yy = y + i;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
|
||||
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module at (x, y).
|
||||
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
for (int8_t i = -2; i <= 2; i++) {
|
||||
for (int8_t j = -2; j <= 2; j++) {
|
||||
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
||||
uint32_t rem = data;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
}
|
||||
|
||||
data = data << 10 | rem;
|
||||
data ^= 0x5412; // uint15
|
||||
|
||||
// Draw first copy
|
||||
for (uint8_t i = 0; i <= 5; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
|
||||
|
||||
for (int8_t i = 9; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
// Draw second copy
|
||||
for (int8_t i = 0; i <= 7; i++) {
|
||||
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
for (int8_t i = 8; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, size - 8, true);
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field (which only has an effect for 7 <= version <= 40).
|
||||
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
|
||||
|
||||
int8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
|
||||
return;
|
||||
|
||||
#else
|
||||
if (version < 7) { return; }
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t rem = version; // version is uint6, in the range [7, 40]
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
}
|
||||
|
||||
uint32_t data = version << 12 | rem; // uint18
|
||||
|
||||
// Draw two copies
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
bool bit = ((data >> i) & 1) != 0;
|
||||
uint8_t a = size - 11 + i % 3, b = i / 3;
|
||||
setFunctionModule(modules, isFunction, a, b, bit);
|
||||
setFunctionModule(modules, isFunction, b, a, bit);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Draw the horizontal and vertical timing patterns
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
|
||||
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(modules, isFunction, 3, 3);
|
||||
drawFinderPattern(modules, isFunction, size - 4, 3);
|
||||
drawFinderPattern(modules, isFunction, 3, size - 4);
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
|
||||
|
||||
if (version > 1) {
|
||||
|
||||
// Draw the numerous alignment patterns
|
||||
|
||||
uint8_t alignCount = version / 7 + 2;
|
||||
uint8_t step;
|
||||
if (version != 32) {
|
||||
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2
|
||||
} else { // C-C-C-Combo breaker!
|
||||
step = 26;
|
||||
}
|
||||
|
||||
uint8_t alignPositionIndex = alignCount - 1;
|
||||
uint8_t alignPosition[alignCount];
|
||||
|
||||
alignPosition[0] = 6;
|
||||
|
||||
uint8_t size = version * 4 + 17;
|
||||
for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) {
|
||||
alignPosition[alignPositionIndex--] = pos;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < alignCount; i++) {
|
||||
for (uint8_t j = 0; j < alignCount; j++) {
|
||||
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
|
||||
continue; // Skip the three finder corners
|
||||
} else {
|
||||
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion(modules, isFunction, version);
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
|
||||
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
|
||||
|
||||
uint32_t bitLength = codewords->bitOffsetOrWidth;
|
||||
uint8_t *data = codewords->data;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Bit index into the data
|
||||
uint32_t i = 0;
|
||||
|
||||
// Do the funny zigzag scan
|
||||
for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6) { right = 5; }
|
||||
|
||||
for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
uint8_t x = right - j; // Actual x coordinate
|
||||
bool upwards = ((right & 2) == 0) ^ (x < 6);
|
||||
uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
|
||||
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
|
||||
i++;
|
||||
}
|
||||
// If there are any remainder bits (0 to 7), they are already
|
||||
// set to 0/false/white when the grid of modules was initialized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Penalty Calculation
|
||||
|
||||
#define PENALTY_N1 3
|
||||
#define PENALTY_N2 3
|
||||
#define PENALTY_N3 40
|
||||
#define PENALTY_N4 10
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
// @TODO: This can be optimized by working with the bytes instead of bits.
|
||||
static uint32_t getPenaltyScore(BitBucket *modules) {
|
||||
uint32_t result = 0;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Adjacent modules in row having same color
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
|
||||
bool colorX = bb_getBit(modules, 0, y);
|
||||
for (uint8_t x = 1, runX = 1; x < size; x++) {
|
||||
bool cx = bb_getBit(modules, x, y);
|
||||
if (cx != colorX) {
|
||||
colorX = cx;
|
||||
runX = 1;
|
||||
|
||||
} else {
|
||||
runX++;
|
||||
if (runX == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runX > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjacent modules in column having same color
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool colorY = bb_getBit(modules, x, 0);
|
||||
for (uint8_t y = 1, runY = 1; y < size; y++) {
|
||||
bool cy = bb_getBit(modules, x, y);
|
||||
if (cy != colorY) {
|
||||
colorY = cy;
|
||||
runY = 1;
|
||||
} else {
|
||||
runY++;
|
||||
if (runY == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runY > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t black = 0;
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
uint16_t bitsRow = 0, bitsCol = 0;
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool color = bb_getBit(modules, x, y);
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
if (x > 0 && y > 0) {
|
||||
bool colorUL = bb_getBit(modules, x - 1, y - 1);
|
||||
bool colorUR = bb_getBit(modules, x, y - 1);
|
||||
bool colorL = bb_getBit(modules, x - 1, y);
|
||||
if (color == colorUL && color == colorUR && color == colorL) {
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Finder-like pattern in rows and columns
|
||||
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
|
||||
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
|
||||
|
||||
// Needs 11 bits accumulated
|
||||
if (x >= 10) {
|
||||
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of black and white modules
|
||||
if (color) { black++; }
|
||||
}
|
||||
}
|
||||
|
||||
// Find smallest k such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
uint16_t total = size * size;
|
||||
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
|
||||
result += PENALTY_N4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Reed-Solomon Generator
|
||||
|
||||
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
|
||||
// Russian peasant multiplication
|
||||
// See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
|
||||
uint16_t z = 0;
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||
z ^= ((y >> i) & 1) * x;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
static void rs_init(uint8_t degree, uint8_t *coeff) {
|
||||
memset(coeff, 0, degree);
|
||||
coeff[degree - 1] = 1;
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
uint16_t root = 1;
|
||||
for (uint8_t i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
coeff[j] = rs_multiply(coeff[j], root);
|
||||
if (j + 1 < degree) {
|
||||
coeff[j] ^= coeff[j + 1];
|
||||
}
|
||||
}
|
||||
root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D)
|
||||
}
|
||||
}
|
||||
|
||||
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
|
||||
// Compute the remainder by performing polynomial division
|
||||
|
||||
//for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
|
||||
//memset(result, 0, degree);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
uint8_t factor = data[i] ^ result[0];
|
||||
for (uint8_t j = 1; j < degree; j++) {
|
||||
result[(j - 1) * stride] = result[j * stride];
|
||||
}
|
||||
result[(degree - 1) * stride] = 0;
|
||||
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
result[j * stride] ^= rs_multiply(coeff[j], factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - QrCode
|
||||
|
||||
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
|
||||
int8_t mode = MODE_BYTE;
|
||||
|
||||
if (isNumeric((char*)text, length)) {
|
||||
mode = MODE_NUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 10 + ((char)(text[i]) - '0');
|
||||
accumCount++;
|
||||
if (accumCount == 3) {
|
||||
bb_appendBits(dataCodewords, accumData, 10);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 or 2 digits remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
|
||||
}
|
||||
|
||||
} else if (isAlphanumeric((char*)text, length)) {
|
||||
mode = MODE_ALPHANUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
|
||||
accumCount++;
|
||||
if (accumCount == 2) {
|
||||
bb_appendBits(dataCodewords, accumData, 11);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 character remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, 6);
|
||||
}
|
||||
|
||||
} else {
|
||||
bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE));
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
bb_appendBits(dataCodewords, (char)(text[i]), 8);
|
||||
}
|
||||
}
|
||||
|
||||
//bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
|
||||
|
||||
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
#else
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
#endif
|
||||
|
||||
uint8_t blockEccLen = totalEcc / numBlocks;
|
||||
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
|
||||
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
|
||||
|
||||
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
|
||||
|
||||
uint8_t result[data->capacityBytes];
|
||||
memset(result, 0, sizeof(result));
|
||||
|
||||
uint8_t coeff[blockEccLen];
|
||||
rs_init(blockEccLen, coeff);
|
||||
|
||||
uint16_t offset = 0;
|
||||
uint8_t *dataBytes = data->data;
|
||||
|
||||
|
||||
// Interleave all short blocks
|
||||
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
|
||||
uint16_t index = i;
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { stride++; }
|
||||
#endif
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Version less than 5 only have short blocks
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
{
|
||||
// Interleave long blocks
|
||||
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
if (blockNum == 0) { stride++; }
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add all ecc blocks, interleaved
|
||||
uint8_t blockSize = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { blockSize++; }
|
||||
#endif
|
||||
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
|
||||
dataBytes += blockSize;
|
||||
}
|
||||
|
||||
memcpy(data->data, result, data->capacityBytes);
|
||||
data->bitOffsetOrWidth = moduleCount;
|
||||
}
|
||||
|
||||
// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits)
|
||||
// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc)
|
||||
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
|
||||
|
||||
|
||||
#pragma mark - Public QRCode functions
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version) {
|
||||
return bb_getGridSizeBytes(4 * version + 17);
|
||||
}
|
||||
|
||||
// @TODO: Return error if data is too big.
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
|
||||
uint8_t size = version * 4 + 17;
|
||||
qrcode->version = version;
|
||||
qrcode->size = size;
|
||||
qrcode->ecc = ecc;
|
||||
qrcode->modules = modules;
|
||||
|
||||
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
|
||||
#else
|
||||
version = LOCK_VERSION;
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits];
|
||||
#endif
|
||||
|
||||
struct BitBucket codewords;
|
||||
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
|
||||
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
|
||||
|
||||
// Place the data code words into the buffer
|
||||
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
|
||||
|
||||
if (mode < 0) { return -1; }
|
||||
qrcode->mode = mode;
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
|
||||
if (padding > 4) { padding = 4; }
|
||||
bb_appendBits(&codewords, 0, padding);
|
||||
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
|
||||
|
||||
// Pad with alternate bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
|
||||
bb_appendBits(&codewords, padByte, 8);
|
||||
}
|
||||
|
||||
BitBucket modulesGrid;
|
||||
bb_initGrid(&modulesGrid, modules, size);
|
||||
|
||||
BitBucket isFunctionGrid;
|
||||
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
|
||||
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
|
||||
|
||||
// Draw function patterns, draw all codewords, do masking
|
||||
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
|
||||
performErrorCorrection(version, eccFormatBits, &codewords);
|
||||
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
|
||||
|
||||
// Find the best (lowest penalty) mask
|
||||
uint8_t mask = 0;
|
||||
int32_t minPenalty = INT32_MAX;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i);
|
||||
int penalty = getPenaltyScore(&modulesGrid);
|
||||
if (penalty < minPenalty) {
|
||||
mask = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR
|
||||
}
|
||||
|
||||
qrcode->mask = mask;
|
||||
|
||||
// Overwrite old format bits
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
|
||||
|
||||
// Apply the final choice of mask
|
||||
applyMask(&modulesGrid, &isFunctionGrid, mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
|
||||
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
|
||||
}
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
|
||||
if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offset = y * qrcode->size + x;
|
||||
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
uint8_t qrcode_getHexLength(QRCode *qrcode) {
|
||||
return ((qrcode->size * qrcode->size) + 7) / 4;
|
||||
}
|
||||
|
||||
void qrcode_getHex(QRCode *qrcode, char *result) {
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __QRCODEGEN_H_
|
||||
#define __QRCODEGEN_H_
|
||||
|
||||
#ifndef __cplusplus
|
||||
typedef unsigned char bool;
|
||||
static const bool false = 0;
|
||||
static const bool true = 1;
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// QR Code Format Encoding
|
||||
#define MODE_NUMERIC 0
|
||||
#define MODE_ALPHANUMERIC 1
|
||||
#define MODE_BYTE 2
|
||||
|
||||
|
||||
// Error Correction Code Levels
|
||||
#define ECC_LOW 0
|
||||
#define ECC_MEDIUM 1
|
||||
#define ECC_QUARTILE 2
|
||||
#define ECC_HIGH 3
|
||||
|
||||
|
||||
// If set to non-zero, this library can ONLY produce QR codes at that version
|
||||
// This saves a lot of dynamic memory, as the codeword tables are skipped
|
||||
#ifndef LOCK_VERSION
|
||||
#define LOCK_VERSION 0
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct QRCode {
|
||||
uint8_t version;
|
||||
uint8_t size;
|
||||
uint8_t ecc;
|
||||
uint8_t mode;
|
||||
uint8_t mask;
|
||||
uint8_t *modules;
|
||||
} QRCode;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version);
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#endif /* __QRCODEGEN_H_ */
|
||||
@@ -253,14 +253,16 @@ void GeoMapView::focus() {
|
||||
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT, nullptr);
|
||||
}
|
||||
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle) {
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude) {
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
altitude_ = altitude;
|
||||
|
||||
// Stupid hack to avoid an event loop
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
geopos.set_altitude(altitude_);
|
||||
geopos.set_report_change(true);
|
||||
|
||||
geomap.set_angle(angle);
|
||||
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
void update_position(float lat, float lon, uint16_t angle);
|
||||
void update_position(float lat, float lon, uint16_t angle, int32_t altitude);
|
||||
|
||||
std::string title() const override { return "Map view"; };
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_qrcode.hpp"
|
||||
#include "qrcodegen.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
|
||||
namespace ui {
|
||||
|
||||
QRCodeImage::QRCodeImage(
|
||||
Rect parent_rect
|
||||
) : Widget { parent_rect }
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QRCodeImage::paint(Painter& painter) {
|
||||
// The structure to manage the QR code
|
||||
QRCode qrcode;
|
||||
int qr_version = 10; // bigger versions aren't handled very well
|
||||
|
||||
// Allocate a chunk of memory to store the QR code
|
||||
uint8_t qrcodeBytes[qrcode_getBufferSize(qr_version)];
|
||||
|
||||
qrcode_initText(&qrcode, qrcodeBytes, qr_version, ECC_HIGH, qr_text_);
|
||||
|
||||
|
||||
display.fill_rectangle(Rect(92, 97, 63, 63), Color::white());
|
||||
|
||||
for (uint8_t y = 0; y < qrcode.size; y++) {
|
||||
for (uint8_t x = 0; x < qrcode.size; x++) {
|
||||
if (qrcode_getModule(&qrcode, x, y)) {
|
||||
display.draw_pixel(Point(95+x,100+y), Color::black());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeView::focus() {
|
||||
button_close.focus();
|
||||
}
|
||||
|
||||
QRCodeView::~QRCodeView() {
|
||||
if (on_close_)
|
||||
on_close_();
|
||||
}
|
||||
|
||||
QRCodeView::QRCodeView(
|
||||
NavigationView& nav,
|
||||
const char * qr_text,
|
||||
const std::function<void(void)> on_close
|
||||
) : nav_ (nav),
|
||||
on_close_(on_close)
|
||||
{
|
||||
|
||||
|
||||
add_children({
|
||||
&text_qr,
|
||||
&qr_code,
|
||||
&button_close});
|
||||
text_qr.set(qr_text);
|
||||
qr_code.set_text(qr_text);
|
||||
|
||||
button_close.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __QRCODE_H__
|
||||
#define __QRCODE_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "qrcodegen.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class QRCodeImage : public Widget {
|
||||
public:
|
||||
|
||||
QRCodeImage(Rect parent_rect);
|
||||
void set_text(const char * qr_text) {
|
||||
qr_text_ = qr_text;
|
||||
}
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
|
||||
private:
|
||||
const char * qr_text_ ;
|
||||
};
|
||||
|
||||
class QRCodeView : public View {
|
||||
public:
|
||||
QRCodeView(
|
||||
NavigationView& nav,
|
||||
const char * qr_text,
|
||||
const std::function<void(void)> on_close = nullptr
|
||||
);
|
||||
~QRCodeView();
|
||||
|
||||
QRCodeView(const QRCodeView&) = delete;
|
||||
QRCodeView(QRCodeView&&) = delete;
|
||||
QRCodeView& operator=(const QRCodeView&) = delete;
|
||||
QRCodeView& operator=(QRCodeView&&) = delete;
|
||||
|
||||
std::string title() const override { return "QR code"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
|
||||
std::function<void(void)> on_close_ { nullptr };
|
||||
|
||||
|
||||
QRCodeImage qr_code {
|
||||
{ 50, 100, 100, 100 }
|
||||
};
|
||||
|
||||
Text text_qr {
|
||||
{ 0 * 8, 10 * 16, 32 * 8, 1 * 8 },
|
||||
"-"
|
||||
};
|
||||
|
||||
Button button_close {
|
||||
{ 9 * 8, 15 * 16, 12 * 8, 3 * 16 },
|
||||
"Back"
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif
|
||||
@@ -45,255 +45,252 @@
|
||||
|
||||
using namespace sd_card;
|
||||
|
||||
namespace ui {
|
||||
namespace ui
|
||||
{
|
||||
|
||||
enum modal_t {
|
||||
INFO = 0,
|
||||
YESNO,
|
||||
YESCANCEL,
|
||||
ABORT
|
||||
};
|
||||
|
||||
class NavigationView : public View {
|
||||
public:
|
||||
std::function<void(const View&)> on_view_changed { };
|
||||
|
||||
NavigationView() = default;
|
||||
|
||||
NavigationView(const NavigationView&) = delete;
|
||||
NavigationView(NavigationView&&) = delete;
|
||||
NavigationView& operator=(const NavigationView&) = delete;
|
||||
NavigationView& operator=(NavigationView&&) = delete;
|
||||
|
||||
bool is_top() const;
|
||||
|
||||
template<class T, class... Args>
|
||||
T* push(Args&&... args) {
|
||||
return reinterpret_cast<T*>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
template<class T, class... Args>
|
||||
T* replace(Args&&... args) {
|
||||
pop();
|
||||
return reinterpret_cast<T*>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
void push(View* v);
|
||||
void replace(View* v);
|
||||
|
||||
void pop();
|
||||
void pop_modal();
|
||||
|
||||
void display_modal(const std::string& title, const std::string& message);
|
||||
void display_modal(const std::string& title, const std::string& message, const modal_t type, const std::function<void(bool)> on_choice = nullptr);
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<View>> view_stack { };
|
||||
Widget* modal_view { nullptr };
|
||||
|
||||
Widget* view() const;
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
};
|
||||
|
||||
class SystemStatusView : public View {
|
||||
public:
|
||||
std::function<void(void)> on_back { };
|
||||
|
||||
SystemStatusView(NavigationView& nav);
|
||||
|
||||
void set_back_enabled(bool new_value);
|
||||
void set_title_image_enabled(bool new_value);
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
private:
|
||||
static constexpr auto default_title = "";
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop {
|
||||
{ 0 * 8, 0 * 16, 240, 16 },
|
||||
Color::dark_grey()
|
||||
enum modal_t
|
||||
{
|
||||
INFO = 0,
|
||||
YESNO,
|
||||
YESCANCEL,
|
||||
ABORT
|
||||
};
|
||||
|
||||
ImageButton button_back {
|
||||
{ 2, 0 * 16, 16, 16 },
|
||||
&bitmap_icon_previous,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
class NavigationView : public View
|
||||
{
|
||||
public:
|
||||
std::function<void(const View &)> on_view_changed{};
|
||||
|
||||
NavigationView() = default;
|
||||
|
||||
NavigationView(const NavigationView &) = delete;
|
||||
NavigationView(NavigationView &&) = delete;
|
||||
NavigationView &operator=(const NavigationView &) = delete;
|
||||
NavigationView &operator=(NavigationView &&) = delete;
|
||||
|
||||
bool is_top() const;
|
||||
|
||||
template <class T, class... Args>
|
||||
T *push(Args &&...args)
|
||||
{
|
||||
return reinterpret_cast<T *>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
template <class T, class... Args>
|
||||
T *replace(Args &&...args)
|
||||
{
|
||||
pop();
|
||||
return reinterpret_cast<T *>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
void push(View *v);
|
||||
void replace(View *v);
|
||||
|
||||
void pop();
|
||||
void pop_modal();
|
||||
|
||||
void display_modal(const std::string &title, const std::string &message);
|
||||
void display_modal(const std::string &title, const std::string &message, const modal_t type, const std::function<void(bool)> on_choice = nullptr);
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<View>> view_stack{};
|
||||
Widget *modal_view{nullptr};
|
||||
|
||||
Widget *view() const;
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
View *push_view(std::unique_ptr<View> new_view);
|
||||
};
|
||||
|
||||
Text title {
|
||||
{ 20, 0, 14 * 8, 1 * 16 },
|
||||
default_title,
|
||||
};
|
||||
class SystemStatusView : public View
|
||||
{
|
||||
public:
|
||||
std::function<void(void)> on_back{};
|
||||
|
||||
ImageButton button_title {
|
||||
{2, 0, 80, 16},
|
||||
&bitmap_titlebar_image,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
SystemStatusView(NavigationView &nav);
|
||||
|
||||
ImageButton button_speaker {
|
||||
{ 17 * 8, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_speaker_mute,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
ImageButton button_stealth {
|
||||
{ 19 * 8, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_stealth,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
/*ImageButton button_textentry {
|
||||
void set_back_enabled(bool new_value);
|
||||
void set_title_image_enabled(bool new_value);
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
private:
|
||||
static constexpr auto default_title = "";
|
||||
|
||||
NavigationView &nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
{0 * 8, 0 * 16, 240, 16},
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_back{
|
||||
{2, 0 * 16, 16, 16},
|
||||
&bitmap_icon_previous,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
Text title{
|
||||
{20, 0, 14 * 8, 1 * 16},
|
||||
default_title,
|
||||
};
|
||||
|
||||
ImageButton button_title{
|
||||
{2, 0, 80, 16},
|
||||
&bitmap_titlebar_image,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_speaker{
|
||||
{17 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_mute,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_stealth{
|
||||
{19 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_stealth,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
/*ImageButton button_textentry {
|
||||
{ 170, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_unistroke,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
};*/
|
||||
|
||||
ImageButton button_camera {
|
||||
{ 21 * 8, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_camera,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
ImageButton button_camera{
|
||||
{21 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_camera,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_sleep{
|
||||
{23 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_sleep,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_bias_tee{
|
||||
{25 * 8, 0, 12, 1 * 16},
|
||||
&bitmap_icon_biast_off,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_clock_status{
|
||||
{27 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_clk_int,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{28 * 8, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
void on_speaker();
|
||||
void on_stealth();
|
||||
void on_bias_tee();
|
||||
//void on_textentry();
|
||||
void on_camera();
|
||||
void on_title();
|
||||
void refresh();
|
||||
void on_clk();
|
||||
|
||||
MessageHandlerRegistration message_handler_refresh{
|
||||
Message::ID::StatusRefresh,
|
||||
[this](const Message *const p)
|
||||
{
|
||||
(void)p;
|
||||
this->refresh();
|
||||
}};
|
||||
};
|
||||
|
||||
ImageButton button_sleep {
|
||||
{ 23 * 8, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_sleep,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
ImageButton button_bias_tee {
|
||||
{ 25 * 8, 0, 12, 1 * 16 },
|
||||
&bitmap_icon_biast_off,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
ImageButton button_clock_status {
|
||||
{ 27 * 8, 0 * 16, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_clk_int,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
SDCardStatusView sd_card_status_view {
|
||||
{ 28 * 8, 0 * 16, 2 * 8, 1 * 16 }
|
||||
class InformationView : public View
|
||||
{
|
||||
public:
|
||||
InformationView(NavigationView &nav);
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
static constexpr auto version_string = "v1.4.3";
|
||||
NavigationView &nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
{0, 0 * 16, 240, 16},
|
||||
{33, 33, 33}};
|
||||
|
||||
Text version{
|
||||
{2, 0, 11 * 8, 16},
|
||||
version_string};
|
||||
|
||||
LiveDateTime ltime{
|
||||
{86, 0, 19 * 8, 16}};
|
||||
};
|
||||
|
||||
void on_speaker();
|
||||
void on_stealth();
|
||||
void on_bias_tee();
|
||||
//void on_textentry();
|
||||
void on_camera();
|
||||
void on_title();
|
||||
void refresh();
|
||||
void on_clk();
|
||||
|
||||
MessageHandlerRegistration message_handler_refresh {
|
||||
Message::ID::StatusRefresh,
|
||||
[this](const Message* const p) {
|
||||
(void)p;
|
||||
this->refresh();
|
||||
}
|
||||
};
|
||||
};
|
||||
class BMPView : public View
|
||||
{
|
||||
public:
|
||||
BMPView(NavigationView &nav);
|
||||
void paint(Painter &) override;
|
||||
void focus() override;
|
||||
|
||||
class InformationView : public View {
|
||||
public:
|
||||
InformationView(NavigationView& nav);
|
||||
void refresh();
|
||||
private:
|
||||
static constexpr auto version_string = "v1.4.2";
|
||||
NavigationView& nav_;
|
||||
private:
|
||||
Text text_info{
|
||||
{4 * 8, 284, 20 * 8, 16},
|
||||
"Version " VERSION_STRING};
|
||||
|
||||
Rectangle backdrop {
|
||||
{ 0, 0 * 16, 240, 16 },
|
||||
{33, 33, 33}
|
||||
Button button_done{
|
||||
{240, 0, 1, 1},
|
||||
""};
|
||||
};
|
||||
|
||||
Text version {
|
||||
{2, 0, 11 * 8, 16},
|
||||
version_string
|
||||
class ReceiversMenuView : public BtnGridView
|
||||
{
|
||||
public:
|
||||
ReceiversMenuView(NavigationView &nav);
|
||||
std::string title() const override { return "Receivers"; };
|
||||
};
|
||||
|
||||
LiveDateTime ltime {
|
||||
{86, 0, 19 * 8, 16}
|
||||
|
||||
class TransmittersMenuView : public BtnGridView
|
||||
{
|
||||
public:
|
||||
TransmittersMenuView(NavigationView &nav);
|
||||
std::string title() const override { return "Transmitters"; };
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
class BMPView : public View {
|
||||
public:
|
||||
BMPView(NavigationView& nav);
|
||||
void paint(Painter&) override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
Text text_info {
|
||||
{ 4*8, 284, 20 * 8, 16 },
|
||||
"Version " VERSION_STRING
|
||||
class UtilitiesMenuView : public BtnGridView
|
||||
{
|
||||
public:
|
||||
UtilitiesMenuView(NavigationView &nav);
|
||||
std::string title() const override { return "Utilities"; };
|
||||
};
|
||||
|
||||
Button button_done {
|
||||
{ 240, 0, 1, 1 },
|
||||
""
|
||||
|
||||
class SystemMenuView : public BtnGridView
|
||||
{
|
||||
public:
|
||||
SystemMenuView(NavigationView &nav);
|
||||
|
||||
private:
|
||||
void hackrf_mode(NavigationView &nav);
|
||||
};
|
||||
};
|
||||
|
||||
class ReceiversMenuView : public BtnGridView {
|
||||
public:
|
||||
ReceiversMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Receivers"; };
|
||||
};
|
||||
class SystemView : public View
|
||||
{
|
||||
public:
|
||||
SystemView(
|
||||
Context &context,
|
||||
const Rect parent_rect);
|
||||
|
||||
class TransmittersMenuView : public BtnGridView {
|
||||
public:
|
||||
TransmittersMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Transmitters"; };
|
||||
};
|
||||
Context &context() const override;
|
||||
|
||||
class UtilitiesMenuView : public BtnGridView {
|
||||
public:
|
||||
UtilitiesMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Utilities"; };
|
||||
};
|
||||
private:
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
NavigationView navigation_view{};
|
||||
Context &context_;
|
||||
};
|
||||
|
||||
class SystemMenuView : public BtnGridView {
|
||||
public:
|
||||
SystemMenuView(NavigationView& nav);
|
||||
private:
|
||||
void hackrf_mode(NavigationView& nav);
|
||||
};
|
||||
|
||||
class SystemView : public View {
|
||||
public:
|
||||
SystemView(
|
||||
Context& context,
|
||||
const Rect parent_rect
|
||||
);
|
||||
|
||||
Context& context() const override;
|
||||
|
||||
private:
|
||||
SystemStatusView status_view { navigation_view };
|
||||
InformationView info_view { navigation_view };
|
||||
NavigationView navigation_view { };
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
/*class NotImplementedView : public View {
|
||||
/*class NotImplementedView : public View {
|
||||
public:
|
||||
NotImplementedView(NavigationView& nav);
|
||||
|
||||
@@ -311,43 +308,43 @@ private:
|
||||
};
|
||||
};*/
|
||||
|
||||
class ModalMessageView : public View {
|
||||
public:
|
||||
ModalMessageView(
|
||||
NavigationView& nav,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const modal_t type,
|
||||
const std::function<void(bool)> on_choice
|
||||
);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void focus() override;
|
||||
class ModalMessageView : public View
|
||||
{
|
||||
public:
|
||||
ModalMessageView(
|
||||
NavigationView &nav,
|
||||
const std::string &title,
|
||||
const std::string &message,
|
||||
const modal_t type,
|
||||
const std::function<void(bool)> on_choice);
|
||||
|
||||
std::string title() const override { return title_; };
|
||||
void paint(Painter &painter) override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
const std::string title_;
|
||||
const std::string message_;
|
||||
const modal_t type_;
|
||||
const std::function<void(bool)> on_choice_;
|
||||
std::string title() const override { return title_; };
|
||||
|
||||
Button button_ok {
|
||||
{ 10 * 8, 14 * 16, 10 * 8, 48 },
|
||||
"OK",
|
||||
private:
|
||||
const std::string title_;
|
||||
const std::string message_;
|
||||
const modal_t type_;
|
||||
const std::function<void(bool)> on_choice_;
|
||||
|
||||
Button button_ok{
|
||||
{10 * 8, 14 * 16, 10 * 8, 48},
|
||||
"OK",
|
||||
};
|
||||
|
||||
Button button_yes{
|
||||
{5 * 8, 14 * 16, 8 * 8, 48},
|
||||
"YES",
|
||||
};
|
||||
|
||||
Button button_no{
|
||||
{17 * 8, 14 * 16, 8 * 8, 48},
|
||||
"NO",
|
||||
};
|
||||
};
|
||||
|
||||
Button button_yes {
|
||||
{ 5 * 8, 14 * 16, 8 * 8, 48 },
|
||||
"YES",
|
||||
};
|
||||
|
||||
Button button_no {
|
||||
{ 17 * 8, 14 * 16, 8 * 8, 48 },
|
||||
"NO",
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_NAVIGATION_H__*/
|
||||
#endif /*__UI_NAVIGATION_H__*/
|
||||
|
||||
@@ -98,6 +98,19 @@ void RecordView::focus() {
|
||||
}
|
||||
|
||||
void RecordView::set_sampling_rate(const size_t new_sampling_rate) {
|
||||
|
||||
/* We are changing "REC" icon background to yellow in BW rec Options >600kHz
|
||||
where we are NOT recording full IQ .C16 files (recorded files are decimated ones).
|
||||
Those decimated recorded files,has not the full IQ samples .
|
||||
are ok as recorded spectrum indication, but they should not be used by Replay app.
|
||||
|
||||
We keep original black background in all the correct IQ .C16 files BW's Options */
|
||||
if (new_sampling_rate > 4800000) { // > BW >600kHz (fs=8*BW), (750kHz ...2750kHz)
|
||||
button_record.set_background(ui::Color::yellow());
|
||||
} else {
|
||||
button_record.set_background(ui::Color::black());
|
||||
}
|
||||
|
||||
if( new_sampling_rate != sampling_rate ) {
|
||||
stop();
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ void SSB::execute(const buffer_s16_t& audio, const buffer_c8_t& buffer) {
|
||||
//default: break;
|
||||
//}
|
||||
|
||||
i *= 64.0f;
|
||||
q *= 64.0f;
|
||||
i *= 256.0f; // Original 64.0f, now x 4 (+12 dB's SSB BB modulation)
|
||||
q *= 256.0f; // Original 64.0f, now x 4 (+12 dB's SSB BB modulation)
|
||||
switch (mode) {
|
||||
case Mode::LSB: re = q; im = i; break;
|
||||
case Mode::USB: re = i; im = q; break;
|
||||
@@ -123,9 +123,9 @@ void AM::execute(const buffer_s16_t& audio, const buffer_c8_t& buffer) {
|
||||
}
|
||||
|
||||
q = sample / 32768.0f;
|
||||
q *= 64.0f;
|
||||
q *= 256.0f; // Original 64.0f,now x4 (+12 dB's BB_modulation in AM & DSB)
|
||||
switch (mode) {
|
||||
case Mode::AM: re = q + 20; im = q + 20; break;
|
||||
case Mode::AM: re = q + 80; im = q + 80; break; // Original DC add +20_DC_level=carrier,now x4 (+12dB's AM carrier)
|
||||
case Mode::DSB: re = q; im = q; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
Regular → Executable
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2017 Furrtek
|
||||
#
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Make airlines.db
|
||||
|
||||
Licensed under GNU GPL v3 (license)[../../../LICENSE]
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt
|
||||
- Run Python 3 script: `./make_airlines_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
|
||||
Executable → Regular
+1191
-69
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
# Make icao24.db
|
||||
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
- Run Python 3 script: `./make_icao24_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
File diff suppressed because it is too large
Load Diff
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 ArjanOnwezen
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create icao24.db, used for ADS-B receiver application, using
|
||||
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
# as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
import csv
|
||||
import unicodedata
|
||||
icao24_codes=bytearray()
|
||||
data=bytearray()
|
||||
row_count=0
|
||||
|
||||
database=open("icao24.db", "wb")
|
||||
|
||||
with open('aircraftDatabase.csv', 'rt') as csv_file:
|
||||
sorted_lines=sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
|
||||
# only store in case enough info is available
|
||||
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
|
||||
icao24_code=row[0][:6].upper()
|
||||
registration=row[1][:8].encode('ascii', 'ignore')
|
||||
manufacturer=row[3][:32].encode('ascii', 'ignore')
|
||||
model=row[4][:32].encode('ascii', 'ignore')
|
||||
# in case icao aircraft type isn't, use ac type like BALL for balloon
|
||||
if len(row[8]) == 3:
|
||||
actype=row[8][:3].encode('ascii', 'ignore')
|
||||
else:
|
||||
actype=row[5][:4].encode('ascii', 'ignore')
|
||||
owner=row[13][:32].encode('ascii', 'ignore')
|
||||
operator=row[9][:32].encode('ascii', 'ignore')
|
||||
#padding
|
||||
icao24_codes=icao24_codes+bytearray(icao24_code+'\0', encoding='ascii')
|
||||
registration_padding=bytearray('\0' * (9 - len(registration)), encoding='ascii')
|
||||
manufacturer_padding=bytearray('\0' * (33 - len(manufacturer)), encoding='ascii')
|
||||
model_padding=bytearray('\0' * (33 - len(model)), encoding='ascii')
|
||||
actype_padding=bytearray('\0' * (5 - len(actype)), encoding='ascii')
|
||||
owner_padding=bytearray('\0' * (33 - len(owner)), encoding='ascii')
|
||||
operator_padding=bytearray('\0' * (33 - len(operator)), encoding='ascii')
|
||||
data=data+bytearray(registration+registration_padding+manufacturer+manufacturer_padding+model+model_padding+actype+actype_padding+owner+owner_padding+operator+operator_padding)
|
||||
row_count+=1
|
||||
|
||||
database.write(icao24_codes+data)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
|
||||
Reference in New Issue
Block a user