mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Fixed ADSB TX frame rotation
This commit is contained in:
parent
482729918d
commit
7f97a090e4
@ -23,17 +23,15 @@
|
|||||||
// Color bitmaps generated with:
|
// Color bitmaps generated with:
|
||||||
// Gimp image > indexed colors (16), then "xxd -i *.bmp"
|
// Gimp image > indexed colors (16), then "xxd -i *.bmp"
|
||||||
|
|
||||||
//TEST: ADS-B tx manchester encoder, velocity and squawk frames
|
//TEST: ADS-B tx velocity and squawk frames
|
||||||
//TEST: Menuview refresh, seems to blink a lot
|
//TEST: Menuview refresh, seems to blink a lot
|
||||||
//TEST: Check AFSK transmit end, skips last bits ?
|
//TEST: Check AFSK transmit end, skips last bits ?
|
||||||
//TEST: Imperial in whipcalc
|
//TEST: Imperial in whipcalc
|
||||||
|
|
||||||
//BUG: ADSB transmit baseband code works only if stuck in a loop (txdone message makes everything go nuts)
|
|
||||||
//BUG: CPLD-related rx ok, tx bad, see portapack.cpp lines 214+ to disable CPLD overlay
|
//BUG: CPLD-related rx ok, tx bad, see portapack.cpp lines 214+ to disable CPLD overlay
|
||||||
//BUG: REPLAY See what's wrong with quality (format, or need for interpolation filter ?)
|
//BUG: REPLAY See what's wrong with quality (format, or need for interpolation filter ?)
|
||||||
//BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
//BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
||||||
//BUG: SCANNER Multiple slices
|
//BUG: SCANNER Multiple slices
|
||||||
//BUG: REPLAY freezes when SD card not present
|
|
||||||
//BUG: RDS doesn't stop baseband when stopping tx ?
|
//BUG: RDS doesn't stop baseband when stopping tx ?
|
||||||
|
|
||||||
//TODO: REPLAY Convert C16 to C8 on M0 core
|
//TODO: REPLAY Convert C16 to C8 on M0 core
|
||||||
|
@ -118,6 +118,8 @@ ADSBPositionView::ADSBPositionView(NavigationView& nav) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
ADSBFrame temp_frame;
|
ADSBFrame temp_frame;
|
||||||
|
|
||||||
encode_frame_pos(temp_frame, ICAO_address, geopos.altitude(),
|
encode_frame_pos(temp_frame, ICAO_address, geopos.altitude(),
|
||||||
@ -149,6 +151,8 @@ ADSBCallsignView::ADSBCallsignView(NavigationView& nav) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADSBCallsignView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
void ADSBCallsignView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
ADSBFrame temp_frame;
|
ADSBFrame temp_frame;
|
||||||
|
|
||||||
encode_frame_id(temp_frame, ICAO_address, callsign);
|
encode_frame_id(temp_frame, ICAO_address, callsign);
|
||||||
@ -175,6 +179,8 @@ ADSBSpeedView::ADSBSpeedView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
ADSBFrame temp_frame;
|
ADSBFrame temp_frame;
|
||||||
|
|
||||||
encode_frame_velo(temp_frame, ICAO_address, field_speed.value(),
|
encode_frame_velo(temp_frame, ICAO_address, field_speed.value(),
|
||||||
@ -193,6 +199,8 @@ ADSBSquawkView::ADSBSquawkView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
ADSBFrame temp_frame;
|
ADSBFrame temp_frame;
|
||||||
(void)ICAO_address;
|
(void)ICAO_address;
|
||||||
|
|
||||||
@ -201,83 +209,35 @@ void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADS
|
|||||||
frame_list.emplace_back(temp_frame);
|
frame_list.emplace_back(temp_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBTxView::focus() {
|
ADSBTXThread::ADSBTXThread(
|
||||||
tab_view.focus();
|
std::vector<ADSBFrame> frames
|
||||||
|
) : frames_ { std::move(frames) }
|
||||||
|
{
|
||||||
|
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ADSBTXThread::static_fn, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBTxView::~ADSBTxView() {
|
ADSBTXThread::~ADSBTXThread() {
|
||||||
transmitter_model.disable();
|
if( thread ) {
|
||||||
baseband::shutdown();
|
chThdTerminate(thread);
|
||||||
|
chThdWait(thread);
|
||||||
|
thread = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBTxView::generate_frames() {
|
msg_t ADSBTXThread::static_fn(void* arg) {
|
||||||
const uint32_t ICAO_address = sym_icao.value_hex_u64();
|
auto obj = static_cast<ADSBTXThread*>(arg);
|
||||||
|
obj->run();
|
||||||
/* This scheme kinda sucks. Each "tab"'s collect_frames method
|
return 0;
|
||||||
* is called to generate its related frame(s). Getting values
|
|
||||||
* from each widget of each tab would be better ?
|
|
||||||
* */
|
|
||||||
view_position.collect_frames(ICAO_address, frames);
|
|
||||||
view_callsign.collect_frames(ICAO_address, frames);
|
|
||||||
view_speed.collect_frames(ICAO_address, frames);
|
|
||||||
view_squawk.collect_frames(ICAO_address, frames);
|
|
||||||
|
|
||||||
// DEBUG: Show how many frames were generated
|
|
||||||
text_frame.set(to_string_dec_uint(frames.size()) + " frame(s).");
|
|
||||||
|
|
||||||
//memset(bin_ptr, 0, 240);
|
|
||||||
|
|
||||||
//auto raw_ptr = frames[0].get_raw_data();
|
|
||||||
|
|
||||||
// The preamble isn't manchester encoded
|
|
||||||
//memcpy(bin_ptr, adsb_preamble, 16);
|
|
||||||
|
|
||||||
// Convert to binary with manchester encoding (1 byte per bit, faster for baseband code)
|
|
||||||
/*for (c = 0; c < 112; c++) {
|
|
||||||
if ((raw_ptr[c >> 3] << (c & 7)) & 0x80) {
|
|
||||||
bin_ptr[(c * 2) + 16] = 1;
|
|
||||||
bin_ptr[(c * 2) + 16 + 1] = 0;
|
|
||||||
} else {
|
|
||||||
bin_ptr[(c * 2) + 16] = 0;
|
|
||||||
bin_ptr[(c * 2) + 16 + 1] = 1;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*manchester_encode(bin_ptr + 16, raw_ptr, 112, 0);
|
|
||||||
|
|
||||||
// Display in hex for debug
|
|
||||||
text_frame.set(to_string_hex_array(frames[0].get_raw_data(), 14));
|
|
||||||
|
|
||||||
button_callsign.set_text(callsign);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBTxView::start_tx() {
|
void ADSBTXThread::run() {
|
||||||
generate_frames();
|
|
||||||
|
|
||||||
transmitter_model.set_sampling_rate(4000000U);
|
|
||||||
transmitter_model.set_rf_amp(true);
|
|
||||||
transmitter_model.set_baseband_bandwidth(10000000);
|
|
||||||
transmitter_model.enable();
|
|
||||||
|
|
||||||
baseband::set_adsb();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ADSBTxView::on_txdone(const bool v) {
|
|
||||||
(void)v;
|
|
||||||
/*if (v) {
|
|
||||||
transmitter_model.disable();
|
|
||||||
tx_view.set_transmitting(false);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void ADSBTxView::rotate_frames() {
|
|
||||||
uint8_t * bin_ptr = shared_memory.bb_data.data;
|
uint8_t * bin_ptr = shared_memory.bb_data.data;
|
||||||
uint8_t * raw_ptr;
|
uint8_t * raw_ptr;
|
||||||
uint32_t frame_index = 0; //, plane_index = 0;
|
uint32_t frame_index = 0; //, plane_index = 0;
|
||||||
uint32_t c; //, regen = 0;
|
//uint32_t regen = 0;
|
||||||
//float offs = 0;
|
//float offs = 0;
|
||||||
|
|
||||||
for (;;) {
|
while( !chThdShouldTerminate() ) {
|
||||||
/*if (!regen) {
|
/*if (!regen) {
|
||||||
regen = 10;
|
regen = 10;
|
||||||
|
|
||||||
@ -294,28 +254,24 @@ void ADSBTxView::rotate_frames() {
|
|||||||
offs += 0.001;
|
offs += 0.001;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
memset(bin_ptr, 0, 240);
|
memset(bin_ptr, 0, 256); // 112 bits * 2 parts = 224 should be enough
|
||||||
|
|
||||||
raw_ptr = frames[frame_index].get_raw_data();
|
raw_ptr = frames_[frame_index].get_raw_data();
|
||||||
|
|
||||||
|
// The preamble isn't manchester encoded
|
||||||
memcpy(bin_ptr, adsb_preamble, 16);
|
memcpy(bin_ptr, adsb_preamble, 16);
|
||||||
|
|
||||||
// Convert to binary (1 byte per bit, faster for baseband code)
|
// Convert to binary (1 byte per bit, faster for baseband code)
|
||||||
for (c = 0; c < 112; c++) {
|
manchester_encode(bin_ptr + 16, raw_ptr, 112, 0);
|
||||||
if ((raw_ptr[c >> 3] << (c & 7)) & 0x80) {
|
|
||||||
bin_ptr[(c * 2) + 16] = 1;
|
// Display in hex for debug
|
||||||
bin_ptr[(c * 2) + 16 + 1] = 0;
|
//text_frame.set(to_string_hex_array(frames[0].get_raw_data(), 14));
|
||||||
} else {
|
|
||||||
bin_ptr[(c * 2) + 16] = 0;
|
|
||||||
bin_ptr[(c * 2) + 16 + 1] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
baseband::set_adsb();
|
baseband::set_adsb();
|
||||||
|
|
||||||
chThdSleepMilliseconds(50);
|
chThdSleepMilliseconds(50);
|
||||||
|
|
||||||
if (frame_index == frames.size()) {
|
if (frame_index == frames_.size()) {
|
||||||
frame_index = 0;
|
frame_index = 0;
|
||||||
//if (regen)
|
//if (regen)
|
||||||
// regen--;
|
// regen--;
|
||||||
@ -325,6 +281,46 @@ void ADSBTxView::rotate_frames() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ADSBTxView::focus() {
|
||||||
|
tab_view.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ADSBTxView::~ADSBTxView() {
|
||||||
|
transmitter_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADSBTxView::generate_frames() {
|
||||||
|
const uint32_t ICAO_address = sym_icao.value_hex_u64();
|
||||||
|
|
||||||
|
frames.clear();
|
||||||
|
|
||||||
|
/* This scheme kinda sucks. Each "tab"'s collect_frames method
|
||||||
|
* is called to generate its related frame(s). Getting values
|
||||||
|
* from each widget of each tab would be better ?
|
||||||
|
* */
|
||||||
|
view_position.collect_frames(ICAO_address, frames);
|
||||||
|
view_callsign.collect_frames(ICAO_address, frames);
|
||||||
|
view_speed.collect_frames(ICAO_address, frames);
|
||||||
|
view_squawk.collect_frames(ICAO_address, frames);
|
||||||
|
|
||||||
|
// Show how many frames were generated
|
||||||
|
//text_frame.set(to_string_dec_uint(frames.size()) + " frame(s).");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADSBTxView::start_tx() {
|
||||||
|
generate_frames();
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(4000000U);
|
||||||
|
transmitter_model.set_rf_amp(true);
|
||||||
|
transmitter_model.set_baseband_bandwidth(10000000);
|
||||||
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
baseband::set_adsb();
|
||||||
|
|
||||||
|
tx_thread = std::make_unique<ADSBTXThread>(frames);
|
||||||
|
}
|
||||||
|
|
||||||
ADSBTxView::ADSBTxView(
|
ADSBTxView::ADSBTxView(
|
||||||
NavigationView& nav
|
NavigationView& nav
|
||||||
) : nav_ { nav }
|
) : nav_ { nav }
|
||||||
@ -349,11 +345,16 @@ ADSBTxView::ADSBTxView(
|
|||||||
view_speed.set_parent_rect(view_rect);
|
view_speed.set_parent_rect(view_rect);
|
||||||
view_squawk.set_parent_rect(view_rect);
|
view_squawk.set_parent_rect(view_rect);
|
||||||
|
|
||||||
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
|
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||||
|
new_view->on_changed = [this](rf::Frequency f) {
|
||||||
|
transmitter_model.set_tuning_frequency(f);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
tx_view.on_start = [this]() {
|
tx_view.on_start = [this]() {
|
||||||
start_tx();
|
start_tx();
|
||||||
tx_view.set_transmitting(true);
|
tx_view.set_transmitting(true);
|
||||||
// Disable for DEBUG
|
|
||||||
//rotate_frames();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tx_view.on_stop = [this]() {
|
tx_view.on_stop = [this]() {
|
||||||
|
@ -152,6 +152,25 @@ private:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ADSBTXThread {
|
||||||
|
public:
|
||||||
|
ADSBTXThread(std::vector<ADSBFrame> frames);
|
||||||
|
~ADSBTXThread();
|
||||||
|
|
||||||
|
ADSBTXThread(const ADSBTXThread&) = delete;
|
||||||
|
ADSBTXThread(ADSBTXThread&&) = delete;
|
||||||
|
ADSBTXThread& operator=(const ADSBTXThread&) = delete;
|
||||||
|
ADSBTXThread& operator=(ADSBTXThread&&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ADSBFrame> frames_ { };
|
||||||
|
Thread* thread { nullptr };
|
||||||
|
|
||||||
|
static msg_t static_fn(void* arg);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
};
|
||||||
|
|
||||||
class ADSBTxView : public View {
|
class ADSBTxView : public View {
|
||||||
public:
|
public:
|
||||||
ADSBTxView(NavigationView& nav);
|
ADSBTxView(NavigationView& nav);
|
||||||
@ -203,8 +222,6 @@ private:
|
|||||||
|
|
||||||
void start_tx();
|
void start_tx();
|
||||||
void generate_frames();
|
void generate_frames();
|
||||||
void rotate_frames();
|
|
||||||
void on_txdone(const bool v);
|
|
||||||
|
|
||||||
ADSBPositionView view_position { nav_ };
|
ADSBPositionView view_position { nav_ };
|
||||||
ADSBCallsignView view_callsign { nav_ };
|
ADSBCallsignView view_callsign { nav_ };
|
||||||
@ -239,13 +256,7 @@ private:
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_tx_done {
|
std::unique_ptr<ADSBTXThread> tx_thread { };
|
||||||
Message::ID::TXDone,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = *reinterpret_cast<const TXDoneMessage*>(p);
|
|
||||||
this->on_txdone(message.done);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -37,64 +37,23 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) {
|
|||||||
|
|
||||||
if (!configured) return;
|
if (!configured) return;
|
||||||
|
|
||||||
/*if (terminate) {
|
for (size_t i = 0; i < buffer.count; i++) {
|
||||||
for (size_t i = 0; i < buffer.count; i++) {
|
if (bit_pos >= (240 << 1)) {
|
||||||
|
configured = false;
|
||||||
|
cur_bit = 0;
|
||||||
|
} else {
|
||||||
|
cur_bit = shared_memory.bb_data.data[bit_pos >> 1];
|
||||||
|
bit_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur_bit) {
|
||||||
|
// Crude AM
|
||||||
|
buffer.p[i] = am_lut[phase & 3];
|
||||||
|
phase++;
|
||||||
|
} else {
|
||||||
buffer.p[i] = { 0, 0 };
|
buffer.p[i] = { 0, 0 };
|
||||||
}
|
}
|
||||||
terminate--;
|
}
|
||||||
if (!terminate) {
|
|
||||||
message.done = true;
|
|
||||||
shared_memory.application_queue.push(message);
|
|
||||||
configured = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {*/
|
|
||||||
|
|
||||||
for (size_t i = 0; i < buffer.count; i++) {
|
|
||||||
|
|
||||||
/*if (active) {
|
|
||||||
if (!sample) {
|
|
||||||
sample = 300;
|
|
||||||
if (bit_pos >= 112) {
|
|
||||||
active = false; // Stop
|
|
||||||
cur_bit = 0;
|
|
||||||
} else {
|
|
||||||
cur_bit = shared_memory.bb_data.data[bit_pos];
|
|
||||||
bit_pos++;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
sample--;
|
|
||||||
|
|
||||||
if (!preamble) {
|
|
||||||
if (sample == 150)
|
|
||||||
cur_bit ^= 1; // Invert
|
|
||||||
}
|
|
||||||
} else {*/
|
|
||||||
/*cur_bit = 0;
|
|
||||||
if (bit_pos >= 16384) {
|
|
||||||
configured = false;
|
|
||||||
message.done = true;
|
|
||||||
shared_memory.application_queue.push(message);
|
|
||||||
}*/
|
|
||||||
if (bit_pos >= (240 << 1)) {
|
|
||||||
configured = false;
|
|
||||||
terminate = 100;
|
|
||||||
cur_bit = 0;
|
|
||||||
} else {
|
|
||||||
cur_bit = shared_memory.bb_data.data[bit_pos >> 1];
|
|
||||||
bit_pos++;
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (cur_bit) {
|
|
||||||
// Crude AM
|
|
||||||
buffer.p[i] = am_lut[phase & 3];
|
|
||||||
phase++;
|
|
||||||
} else {
|
|
||||||
buffer.p[i] = { 0, 0 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBTXProcessor::on_message(const Message* const p) {
|
void ADSBTXProcessor::on_message(const Message* const p) {
|
||||||
@ -103,9 +62,7 @@ void ADSBTXProcessor::on_message(const Message* const p) {
|
|||||||
if (message.id == Message::ID::ADSBConfigure) {
|
if (message.id == Message::ID::ADSBConfigure) {
|
||||||
bit_pos = 0;
|
bit_pos = 0;
|
||||||
phase = 0;
|
phase = 0;
|
||||||
active = true;
|
|
||||||
configured = true;
|
configured = true;
|
||||||
terminate = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,6 @@ private:
|
|||||||
{ 0, -127 }
|
{ 0, -127 }
|
||||||
};
|
};
|
||||||
|
|
||||||
bool active { };
|
|
||||||
uint32_t terminate { };
|
|
||||||
uint32_t bit_pos { 0 };
|
uint32_t bit_pos { 0 };
|
||||||
uint32_t cur_bit { 0 };
|
uint32_t cur_bit { 0 };
|
||||||
uint32_t phase { 0 };
|
uint32_t phase { 0 };
|
||||||
|
@ -70,13 +70,16 @@ FormattedSymbols format_symbols(
|
|||||||
return { hex_data, hex_error };
|
return { hex_data, hex_error };
|
||||||
}
|
}
|
||||||
|
|
||||||
void manchester_encode(uint8_t * dest, uint8_t * src, size_t length, const size_t sense) {
|
void manchester_encode(uint8_t * dest, uint8_t * src, const size_t length, const size_t sense) {
|
||||||
uint_fast8_t part = sense ? 0 : 0xFF;
|
uint8_t part = sense ? 0 : 0xFF;
|
||||||
|
|
||||||
for (size_t c = 0; c < length; c++) {
|
for (size_t c = 0; c < length; c++) {
|
||||||
if ((src[c >> 3] << (c & 7)) & 0x80) {
|
if ((src[c >> 3] << (c & 7)) & 0x80) {
|
||||||
*(dest++) = part;
|
*(dest++) = part;
|
||||||
*(dest++) = ~part;
|
*(dest++) = ~part;
|
||||||
|
} else {
|
||||||
|
*(dest++) = ~part;
|
||||||
|
*(dest++) = part;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,6 @@ FormattedSymbols format_symbols(
|
|||||||
const ManchesterDecoder& decoder
|
const ManchesterDecoder& decoder
|
||||||
);
|
);
|
||||||
|
|
||||||
void manchester_encode(uint8_t * dest, uint8_t * src, size_t length, const size_t sense = 0);
|
void manchester_encode(uint8_t * dest, uint8_t * src, const size_t length, const size_t sense = 0);
|
||||||
|
|
||||||
#endif/*__MANCHESTER_H__*/
|
#endif/*__MANCHESTER_H__*/
|
||||||
|
Loading…
Reference in New Issue
Block a user