Docking ISS menggunakan JavaScript dan kompas

Perusahaan SpaceX, yang didirikan oleh Ilon Mask yang terkenal kejam, merilis simulator untuk docking manual pesawat ruang angkasa Dragon Crew dengan ISS. Jika semuanya berjalan sesuai rencana, docking akan berlangsung pada 27 Mei 2020. Ini akan berlangsung dalam mode otomatis penuh, tetapi kru akan dapat beralih ke kontrol manual. Sebenarnya, ini adalah mode manual yang direproduksi dalam simulator.

Simulator itu sendiri terletak di situs dan merupakan mainan yang agak bermasalah untuk punggungan pertama ... Pesawat

ulang-alik mencoba terbang dengan cara yang salah ... Dan akurasi yang Anda butuhkan untuk masuk ke gateway adalah 20 cm ... sepanjang tiga sumbu, serta kecepatan sudut, kecepatan perpindahan. dll.

Perasaan patriotik mulai bermain dalam diri saya dan entah bagaimana itu memalukan bagi kekuatan ruang sebelumnya, dan saya menganggap simulator ini sebagai sebuah tantangan. Karena Musk memutuskan untuk menunjukkan kerumitan docking, dan kesulitan apa yang dialami insinyur mereka untuk membuat program docking otomatis, saya memutuskan untuk menulis, di waktu senggang saya, program JavaScript yang dengan mudah menghubungkan Dragon dan ISS dalam simulator ini.

Bagaimana Anda suka itu, Elon Musk?

gambar
Merokok buruk bagi kesehatan Anda

Perhatian! Algoritma ini adalah "olok-olok", dan tidak dimaksudkan untuk digunakan dalam kondisi nyata. Penulis tidak bertanggung jawab atas kerugian langsung atau tidak langsung yang ditimbulkan oleh pesawat ruang angkasa Anda atau benda lain menggunakan algoritma ini.




Pertama, sedikit sejarah.

Ini adalah fakta yang terkenal bahwa pesawat ulang-alik Buran kami yang dapat digunakan kembali sangat mirip dengan pesawat ulang-alik Amerika. Dan juga diketahui bahwa ia hanya terbang sekali, tidak seperti "rekan" Amerika. Tetapi hanya sedikit orang yang tahu bahwa satu-satunya penerbangannya adalah tanpa awak. Dia sendiri berangkat, dia mendarat dan dia melakukan semua ini dalam kondisi cuaca yang sangat buruk.

American Shuttles selalu mendarat hanya dalam mode manual. Ini mengejutkan mengingat fakta bahwa komputer dulu tidak lebih kuat dari kalkulator. Itu, secara teori, seharusnya tidak sulit, pikir tadi malam.



Tapi mari kita langsung ke intinya. Apa itu simulator di situs web SpaceX .

Pada awalnya, kita melihat informasi umum bahwa penyimpangan dalam semua parameter harus berada dalam 0,2 meter (20 cm). Mengingat ukuran stasiun dan kapal, ini adalah batasan yang cukup serius.

Kami mulai simulator dan melihat.



Di atas, ke kanan dan di bawah lingkaran pusat adalah penyimpangan sudut kapal sepanjang tiga sumbu.
Hijau - nilai saat ini.

Biru - kecepatan per detik perubahannya.

Waktu offset relatif ke gateway dalam meter. Tidak ada kecepatan perpindahan ...

Kontroler di bagian bawah layar adalah tombol dengan duplikasi mereka pada keyboard.

Jadi kita akan mulai dengan mereka analisis program sebagai yang paling tidak menarik.

Tata letak tombol keyboard.



Blok kiri bertanggung jawab atas offset relatif terhadap gateway, tetapi yang kanan untuk offset relatif terhadap sumbu.

Kami menulis, atau menemukan di jaringan, kode yang dapat meniru klik keyboard pada dokumen. Dalam kasus saya, kode itu terlihat seperti ini.

function simulateKey(keyCode, type, modifiers) {
    var evtName = (typeof (type) === "string") ? "key" + type : "keydown";
    var modifier = (typeof (modifiers) === "object") ? modifier : {};

    var event = document.createEvent("HTMLEvents");
    event.initEvent(evtName, true, false);
    event.keyCode = keyCode;

    for (var i in modifiers) {
        event[i] = modifiers[i];
    }
    document.dispatchEvent(event);
}

function keyPress(keyCode) {
    simulateKey(keyCode)
    setTimeout(() => simulateKey(keyCode, "up"), 15);
}

Kami menulis kode tombol:
let _accelerator = 69;
let _brake = 81;
let _translateLeft = 65;
let _translateRigth = 68;
let _translateUp = 87;
let _translateDown = 83;

let _left = 37;
let _rigth = 39;
let _up = 38;
let _down = 40;

let _rollRigth = 105;
let _rollLeft = 103;

Setiap sistem kontrol melibatkan kerja dalam satu siklus. Mari kita membuatnya menjadi yang termudah, dengan peningkatan 200 milidetik. Kami akan mengatur penghitung untuk satu, kami masih akan membutuhkannya.

let index = 0;
function A() {

    index++;
    setTimeout(A, 200);
}

A();

Mari kita kembali ke struktur situs.

Fitur yang menarik adalah bahwa ISS digambar di atas kanvas, tetapi informasi tentang keadaan pesawat ruang angkasa kita digambar dengan markup biasa. Rasanya seperti pengembang situs berasumsi bahwa akan ada penggemar yang sama yang ingin "mengotomatisasi" permainan dan memberi mereka kesempatan seperti itu ... Atau mungkin dengan menandainya, itu bodoh, lebih mudah dilakukan.



Jadi, mari kita tambahkan beberapa garis cahaya lagi untuk mendapatkan informasi tentang keadaan pesawat ruang angkasa kita.


    let range = parseFloat($("#range .rate").outerText.split(' '));

    let yDistance = parseFloat($("#y-range .distance").outerText.split(' ')[0]);
    let zDistance = parseFloat($("#z-range .distance").outerText.split(' ')[0]);

    let rollError = parseFloat($("#roll .error").outerText);
    let pitchError = parseFloat($("#pitch .error").outerText);
    let yawError = parseFloat($("#yaw .error").outerText);

    let rate = parseFloat($("#rate .rate").outerText.split(' ')[0]);

Seperti yang Anda lihat, saya tidak mencabut semuanya. Saya hanya mengeluarkan nilai offset, tetapi saya tidak mengambil tingkat perubahan nilai, dan itulah sebabnya ...

Faktanya, ini adalah iterasi ketiga dari algoritma. Pada awalnya, ini adalah opsi sederhana, yang setiap 200 milidetik mengambil informasi tentang kondisi kapal dan menyesuaikannya dengan 0. Tampaknya

seperti ini.

if (rollError !== -rollSpeed) {
        const rollLimit = (Math.abs(rollError) / 10);
        if (0 < rollError && rollSpeed < rollLimit) {
            keyPress(_rollRigth);
        } else if (rollError < -0 && -rollLimit < rollSpeed) {
            keyPress(_rollLeft);
        }
    }

Dan faktanya, dia cukup pekerja. Khusus untuk perpindahan sudut. Dan untuk perpindahan di sepanjang sumbu, saya menggunakan opsi ini.

   const zLimit = (Math.abs(yawError) / 10);
        if (0 < zDistance && zSpeed < zLimit) {
            keyPress(_translateDown);
        } else if (zDistance < 0 && -1 < zSpeed) {
            keyPress(_translateUp);
        }

Kecepatan perpindahan kapal relatif terhadap setiap sumbu tidak ditampilkan, tetapi tidak sulit untuk dihitung.

function carculateSpeed() {
    let yDistance = parseFloat($("#y-range .distance").outerText.split(' ')[0]);
    let zDistance = parseFloat($("#z-range .distance").outerText.split(' ')[0]);

    ySpeed = yPrev - yDistance;
    yPrev = yDistance;

    zSpeed = zPrev - zDistance;
    zPrev = zDistance;
    setTimeout(carculateSpeed, 1000);
}
carculateSpeed();

Dan ternyata lumayan lumayan. Sirkuit kontrol umpan balik klasik. Dan ketika kapal berada jauh dari ISS, kami terbang dengan cukup lancar ke diri kami sendiri [seperti yang saya rasakan]. Tetapi masalah mulai di dekat kapal itu sendiri. Faktanya, kapal itu sangat sosis dan secara fisik tidak mungkin untuk mendapatkan akurasi 0,2 meter. Faktanya adalah bahwa pencampuran kapal kami terjadi ... katakanlah di ruang yang berkesinambungan (dengan akurasi tinggi), tetapi kami hanya melihat sepersepuluh dari itu. Dan tentu saja, mencoba merespons mereka setiap 200 milidetik, kami mendapatkan tindakan regulasi yang sangat kuat. Kami telah menusuk tombol terlalu banyak pada penyimpangan sedikit pun. Dan semakin dekat ke kapal, semakin kuat nilai offset mulai melompat dan kami benar-benar mengguncang kapal lebih ... Meningkatkan amplitudo pergerakannya ...

Itu perlu di suatu tempat untuk mengambil akurasi yang hilang. Pada iterasi kedua dalam menyelesaikan masalah ini, saya sendiri mencoba menghitung yang sudah hanya berdasarkan perpindahan kecepatan. Dan ya, sepertinya berhasil juga, tetapi ini tidak menyelesaikan masalah dengan gerakan ...

Apa esensi dari masalah gerakan? Nah, lihat, kami temukan di luar angkasa dan dengan mengklik tombol kontrol kami memberikan akselerasi ke kapal dalam satu pesawat atau yang lain. Namun begitu kami melepas tombol, gerakannya tidak berhenti. Di luar angkasa, karena ruang hampa, tidak ada resistensi. Dan segera setelah kami memberikan dorongan (dengan menekan sebuah tombol) kapal mulai bergerak dengan kecepatan ini ... Dan itu perlu dihentikan entah bagaimana. Berhenti di simulator cukup mudah - Anda harus memberikan dorongan terbalik.

Tetapi pada iterasi kedua dari solusi, peningkatan akurasi kesalahan tidak memberi saya jawaban tentang bagaimana menyesuaikan kecepatan ...

Dan di sini kami membutuhkan "kompas". Kapten kapal / kapal apa pun harus menghitung rute sebelumnya. Jika dia memberi perintah untuk memperlambat setelah dia masuk ke pelabuhan, maka dia tidak mungkin untuk menambatkan perhiasan. Dan kita hanya perlu ini.

Kita perlu menghitung rute, kapten biasanya melakukan ini dengan kompas dengan keadaan diskrit tipsnya. Dan kami akan melakukan hal yang sama. Kami akan menghitung rute untuk satu detik ke depan, yang akan mencakup lima iterasi penekanan tombol atau tidak.

    if (index % 5 === 0) {
        calculatePath(roll, rollError);
        calculatePath(pitch, pitchError);
        calculatePath(yaw, yawError);
        calculatePath(y, yDistance);
        calculatePath(z, zDistance);
    }

Fungsi carculatePath , berdasarkan nilai deviasinya saat ini, menghitung 5 langkah yang secara teori harus mengurangi penyimpangan ini menjadi 0. Tidak harus pada iterasi ini, tetapi setiap kali kita harus mendekati nol yang berharga dalam kisi yang lebih rinci.

function calculatePath(data, value) {
    data.path = [];
    if (data.prev === value) {
        data.speed = 0;
    }
    for (let i = 0; i < 5; i++) {
        if (0 < value + data.speed * (i + 1)) {
            data.speed -= 0.1;
            data.path.push(-1);
        } else if (value + data.speed * (i + 1) < -0) {
            data.speed += 0.1;
            data.path.push(1);
        } else if (i > 0) {
            if (0 < data.speed) {
                data.speed -= 0.1;
                data.path.push(-1);
            } else if (data.speed < 0) {
                data.speed += 0.1;
                data.path.push(1);
            } else {
                data.path.push(0);
            }
        } else {
            data.path.push(0);
        }
    }
    data.prev = value;
}

Itu saja, kami menghitung rute setiap "sama" detik (indeks% 5 === 0) dan sekarang Anda hanya perlu mengikuti kursus ini.


 let rollStep = roll.path[index % 5];
    if (0 < rollStep) {
        keyPress(_rollLeft);
    } else if (rollStep < 0) {
        keyPress(_rollRigth);
    }

    let pitchStep = pitch.path[index % 5];
    if (0 < pitchStep) {
        keyPress(_up);
    } else if (pitchStep < 0) {
        keyPress(_down);
    }

    let yawStep = yaw.path[index % 5];
    if (0 < yawStep) {
        keyPress(_left);
    } else if (yawStep < 0) {
        keyPress(_rigth);
    }

    let yStep = y.path[index % 5];
    if (0 < yStep) {
        keyPress(_translateRigth);
    } else if (yStep < 0) {
        keyPress(_translateLeft);
    }

    let zStep = z.path[index % 5];
    if (0 < zStep) {
        keyPress(_translateUp);
    } else if (zStep < 0) {
        keyPress(_translateDown);
    }

Satu-satunya perhitungan yang bertahan sejak iterasi pertama adalah pendekatan ke kapal.

Itu saja, oke, kami bergerak maju dengan kecepatan yang relatif rendah

    const rangeLimit = Math.min(Math.max((Math.abs(range) / 100), 0.05), 2);
    if (-rate < rangeLimit) {
        keyPress(_accelerator);
    } else if (-rangeLimit < -rate) {
        keyPress(_brake);
    }

Di bawah spoiler adalah kode lengkap. Anda dapat memeriksa kinerjanya sendiri di situs web iss-sim.spacex.com

Kode lengkap
function simulateKey(keyCode, type, modifiers) {
    var evtName = (typeof (type) === "string") ? "key" + type : "keydown";
    var modifier = (typeof (modifiers) === "object") ? modifier : {};

    var event = document.createEvent("HTMLEvents");
    event.initEvent(evtName, true, false);
    event.keyCode = keyCode;

    for (var i in modifiers) {
        event[i] = modifiers[i];
    }
    document.dispatchEvent(event);
}

function keyPress(keyCode) {
    simulateKey(keyCode)
    setTimeout(() => simulateKey(keyCode, "up"), 15);
}

let _accelerator = 69;
let _brake = 81;
let _translateLeft = 65;
let _translateRigth = 68;
let _translateUp = 87;
let _translateDown = 83;

let _left = 37;
let _rigth = 39;
let _up = 38;
let _down = 40;

let _rollRigth = 105;
let _rollLeft = 103;

let index = 0;

roll = {
    path: [0, 0, 0, 0, 0],
    prev: 0,
    speed: 0,
}

pitch = {
    path: [0, 0, 0, 0, 0],
    prev: 0,
    speed: 0,
}

yaw = {
    path: [0, 0, 0, 0, 0],
    prev: 0,
    speed: 0,
}

z = {
    path: [0, 0, 0, 0, 0],
    prev: 0,
    speed: 0,
}

y = {
    path: [0, 0, 0, 0, 0],
    prev: 0,
    speed: 0,
}

function calculatePath(data, value) {
    data.path = [];
    if (data.prev === value) {
        data.speed = 0;
    }
    for (let i = 0; i < 5; i++) {
        if (0 < value + data.speed * (i + 1)) {
            data.speed -= 0.1;
            data.path.push(-1);
        } else if (value + data.speed * (i + 1) < -0) {
            data.speed += 0.1;
            data.path.push(1);
        } else if (i > 0) {
            if (0 < data.speed) {
                data.speed -= 0.1;
                data.path.push(-1);
            } else if (data.speed < 0) {
                data.speed += 0.1;
                data.path.push(1);
            } else {
                data.path.push(0);
            }
        } else {
            data.path.push(0);
        }
    }
    data.prev = value;
}

function A() {
    let range = parseFloat($("#range .rate").textContent.split(' '));

    let yDistance = parseFloat($("#y-range .distance").textContent.split(' ')[0]);
    let zDistance = parseFloat($("#z-range .distance").textContent.split(' ')[0]);

    let rollError = parseFloat($("#roll .error").textContent);
    let pitchError = parseFloat($("#pitch .error").textContent);
    let yawError = parseFloat($("#yaw .error").textContent);

    let rate = parseFloat($("#rate .rate").textContent.split(' ')[0]);

    if (index % 5 === 0) {
        calculatePath(roll, rollError);
        calculatePath(pitch, pitchError);
        calculatePath(yaw, yawError);
        calculatePath(y, yDistance);
        calculatePath(z, zDistance);
    }

    let rollStep = roll.path[index % 5];
    if (0 < rollStep) {
        keyPress(_rollLeft);
    } else if (rollStep < 0) {
        keyPress(_rollRigth);
    }

    let pitchStep = pitch.path[index % 5];
    if (0 < pitchStep) {
        keyPress(_up);
    } else if (pitchStep < 0) {
        keyPress(_down);
    }

    let yawStep = yaw.path[index % 5];
    if (0 < yawStep) {
        keyPress(_left);
    } else if (yawStep < 0) {
        keyPress(_rigth);
    }

    let yStep = y.path[index % 5];
    if (0 < yStep) {
        keyPress(_translateRigth);
    } else if (yStep < 0) {
        keyPress(_translateLeft);
    }

    let zStep = z.path[index % 5];
    if (0 < zStep) {
        keyPress(_translateUp);
    } else if (zStep < 0) {
        keyPress(_translateDown);
    }

    const rangeLimit = Math.min(Math.max((Math.abs(range) / 100), 0.05), 2);
    if (-rate < rangeLimit) {
        keyPress(_accelerator);
    } else if (-rangeLimit < -rate) {
        keyPress(_brake);
    }

    index++;
    setTimeout(A, 200);
}

A();


Sebenarnya semuanya. Terima kasih sudah membaca :)



PS


Ya, saya melakukan sedikit sombong dan mengolok-olok. Jangan menganggap serius artikel ini, saya hanya ingin merasa seperti astronot [atau penerbang angkasa], seperti Gagarin Yuri Alekseevich ... Meskipun ini kemungkinan besar adalah pekerjaan rekayasa dari Sergey Pavlovich Korolev , yang sayangnya tidak pernah mengunjungi ruang yang ia impikan ...

All Articles