Andocken der ISS mit JavaScript und Kompass

Die von der berüchtigten Ilon Mask gegründete Firma SpaceX veröffentlichte einen Simulator für das manuelle Andocken des Raumfahrzeugs Crew Dragon an die ISS. Wenn alles nach Plan läuft, erfolgt das Andocken am 27. Mai 2020. Es findet im vollautomatischen Modus statt, aber die Besatzung kann auf manuelle Steuerung umschalten. Tatsächlich ist es der manuelle Modus, der im Simulator wiedergegeben wird.

Der Simulator selbst befindet sich auf der Baustelle und ist ein ziemlich problematisches Spielzeug für den ersten Grat ... Das

Space Shuttle versucht, in die falsche Richtung zu fliegen ... Und die Genauigkeit, mit der Sie in das Gateway gelangen müssen, beträgt 20 cm ... entlang drei Achsen sowie Winkelgeschwindigkeit und Verschiebungsgeschwindigkeit usw.

Patriotische Gefühle begannen in mir zu spielen und irgendwie wurde es eine Schande für die ehemalige Weltraummacht, und ich nahm diesen Simulator als Herausforderung. Da Musk sich entschied, die Komplexität des Andockens und die Schwierigkeiten ihrer Ingenieure bei der Erstellung eines automatischen Andockprogramms aufzuzeigen, beschloss ich, in meiner Freizeit ein JavaScript-Programm zu schreiben, das Dragon und die ISS in diesem Simulator problemlos miteinander verbindet.

Wie gefällt dir das, Elon Musk?

Bild
Rauchen ist schlecht für deine Gesundheit

Beachtung! Dieser Algorithmus ist "Scherz" und war nicht für die Verwendung unter realen Bedingungen vorgesehen. Der Autor ist nicht verantwortlich für direkte oder indirekte Verluste, die Ihrem Raumfahrzeug oder anderen Objekten mit diesem Algorithmus entstehen.




Zunächst ein wenig Geschichte.

Es ist eine bekannte Tatsache, dass unser wiederverwendbares Space Shuttle Buran einem amerikanischen Shuttle sehr ähnlich war. Und es ist auch bekannt, dass er im Gegensatz zu den amerikanischen "Gegenstücken" nur einmal geflogen ist. Aber nur wenige wissen, dass sein einziger Flug unbemannt war. Er selbst startete, landete und tat dies alles bei sehr schlechten Wetterbedingungen.

American Shuttles landeten immer nur im manuellen Modus. Dies war angesichts der Tatsache überraschend, dass Computer früher nicht leistungsfähiger waren als ein Taschenrechner. Das sollte theoretisch nicht schwierig sein, dachte letzte Nacht.



Aber kommen wir zum Punkt. Was ist ein Simulator auf der SpaceX Website .

Zu Beginn sehen wir allgemeine Informationen, dass die Abweichung in allen Parametern innerhalb von 0,2 Metern (20 cm) liegen sollte. Angesichts der Größe der Station und des Schiffes ist dies eine ziemlich schwerwiegende Einschränkung.

Wir starten den Simulator und sehen.



Oben rechts und unterhalb des Mittelkreises befindet sich die Winkelabweichung des Schiffes entlang drei Achsen.
Grün - aktueller Wert.

Blau - die Geschwindigkeit pro Sekunde, mit der es sich ändert.

Linker Versatz relativ zum Gateway in Metern. Es gibt keine Verschiebungsgeschwindigkeit ...

Die Controller am unteren Bildschirmrand sind Schaltflächen mit ihrer Duplizierung auf der Tastatur.

Wir beginnen mit ihnen die Analyse des Programms als am wenigsten interessant.

Das Layout der Tastaturtasten.



Der linke Block ist für den Versatz relativ zum Gateway verantwortlich, der rechte für den Versatz relativ zu den Achsen.

Wir schreiben oder finden im Netzwerk einen Code, der Tastaturklicks auf Dokumente emulieren kann. In meinem Fall sah der Code so aus.

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);
}

Wir schreiben die Button Codes:
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;

Jedes Steuerungssystem beinhaltet die Arbeit in einem Zyklus. Machen wir es uns am einfachsten in Schritten von 200 Millisekunden. Wir organisieren einen Schalter für einen, wir werden ihn noch brauchen.

let index = 0;
function A() {

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

A();

Kehren wir zur Struktur der Site zurück.

Das interessante Merkmal ist, dass die ISS auf Leinwand gezeichnet wird, die Informationen über den Zustand unseres Raumfahrzeugs jedoch mit dem üblichen Markup gezeichnet werden. Es scheint, als hätten die Entwickler der Website angenommen, dass es ähnliche Enthusiasten geben würde, die das Spiel "automatisieren" und ihnen eine solche Gelegenheit geben wollen ... Oder vielleicht war es dumm und einfacher, es zu markieren, indem man es markiert.



Fügen wir also noch ein paar Lichtlinien hinzu, um Informationen über den Zustand unseres Raumfahrzeugs zu erhalten.


    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]);

Wie Sie sehen, habe ich nicht alles herausgezogen. Ich habe nur die Versatzwerte herausgezogen, aber die Änderungsrate der Werte nicht berücksichtigt. Und hier ist der Grund ...

Eigentlich ist dies die dritte Iteration des Algorithmus. Zunächst handelt es sich um eine einfache Option, bei der alle 200 Millisekunden Informationen über den Zustand des Schiffes erfasst und auf 0 eingestellt werden. Es

sah so aus.

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

Und tatsächlich war er ein ziemlicher Arbeiter. Besonders für Winkelverschiebungen. Und für die Verschiebung entlang der Achsen habe ich diese Option verwendet.

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

Die Geschwindigkeit der Schiffsverschiebung relativ zu jeder Achse wird nicht angezeigt, ist jedoch nicht schwer zu berechnen.

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();

Und es stellte sich ziemlich erträglich heraus. Klassische Rückkopplungsregelung. Und während sich das Schiff in einiger Entfernung von der ISS befand, flogen wir ziemlich reibungslos zu uns selbst [wie es mir damals schien]. Aber Probleme begannen in der Nähe des Schiffes. Tatsächlich war das Schiff sehr wurstig und es war physikalisch unmöglich, eine Genauigkeit von 0,2 Metern zu erreichen. Tatsache ist, dass die Vermischung unseres Schiffes stattgefunden hat ... sagen wir im kontinuierlichen Raum (mit großer Genauigkeit), aber wir haben nur Zehntel davon gesehen. Und natürlich haben wir beim Versuch, alle 200 Millisekunden darauf zu reagieren, sehr strenge regulatorische Maßnahmen ergriffen. Wir haben die Tasten bei der geringsten Abweichung zu oft gedrückt. Und je näher am Schiff, desto stärker begannen die Versatzwerte zu springen und wir schaukelten das Schiff sogar noch mehr ... Erhöhen der Amplitude seiner Bewegung ...

Es war irgendwo notwendig, die fehlende Genauigkeit zu nehmen. In der zweiten Iteration zur Lösung dieses Problems habe ich selbst versucht, bereits nur anhand der Geschwindigkeitsverschiebung zu berechnen. Und ja, es schien auch ziemlich gut zu funktionieren, aber dies löste das Problem mit der Bewegung nicht ...

Was ist das Wesentliche des Bewegungsproblems? Schauen Sie, wir finden im Weltraum und durch Klicken auf die Steuertasten beschleunigen wir das Schiff in der einen oder anderen Ebene. Sobald wir den Knopf loslassen, hört die Bewegung jedoch nicht auf. Im Weltraum gibt es aufgrund des Vakuums keinen Widerstand. Und sobald wir einen Impuls gaben (per Knopfdruck), begann sich das Schiff mit dieser Geschwindigkeit zu bewegen ... Und es muss irgendwie gestoppt werden. Das Anhalten im Simulator ist ganz einfach - Sie müssen einen umgekehrten Impuls geben.

Aber bei der zweiten Iteration der Lösung gab mir die erhöhte Genauigkeit des Fehlers keine Antwort darauf, wie die Geschwindigkeit angepasst werden sollte ...

Und hier brauchten wir einen „Kompass“. Der Kapitän eines Schiffes muss die Route im Voraus berechnen. Wenn er den Befehl gibt, langsamer zu fahren, nachdem er den Hafen betreten hat, ist es unwahrscheinlich, dass er Schmuck festmacht. Und das brauchen wir einfach.

Wir müssen die Route berechnen, Kapitäne tun dies normalerweise mit einem Kompass mit einem diskreten Zustand seiner Spitzen. Und wir werden das Gleiche tun. Wir werden die Route für eine Sekunde im Voraus berechnen, die fünf Iterationen von Tastendrücken enthält oder nicht.

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

Die carculatePath- Funktion berechnet basierend auf ihrem aktuellen Abweichungswert 5 Schritte, die diese Abweichung theoretisch auf 0 reduzieren sollten. Nicht unbedingt bei dieser Iteration, aber jedes Mal sollten wir uns der geschätzten Null in unserem eigenen detaillierteren Raster nähern.

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;
}

Das ist alles, wir berechnen die Route jede "gleiche" Sekunde (Index% 5 === 0) und jetzt müssen Sie nur noch diesen Kurs absolvieren.


 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);
    }

Die einzige Berechnung, die seit der ersten Iteration überlebt hat, ist die Annäherung an das Schiff.

Das ist alles, okay, wir bewegen uns mit relativ geringer Geschwindigkeit vorwärts

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

Unter dem Spoiler befindet sich der vollständige Code. Sie können die Leistung selbst auf der Website iss-sim.spacex.com überprüfen

Vollständiger Code
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();


Eigentlich alles. Vielen Dank fürs Lesen :)



PS


Ja, ich habe ein bisschen Highpan gemacht und mich lustig gemacht. Nehmen Sie den Artikel nicht ernst, ich wollte mich nur wie ein Astronaut [oder Weltraumflieger] fühlen, wie Gagarin Yuri Alekseevich ... Obwohl dies eher die Ingenieurarbeit von Sergey Pavlovich Korolev ist , der leider nie den Raum besucht hat, von dem er geträumt hat ...

All Articles