Amarrage de l'ISS à l'aide de JavaScript et d'une boussole

La société SpaceX, fondée par le célèbre Ilon Mask, a publié un simulateur pour l'amarrage manuel du vaisseau spatial Crew Dragon avec l'ISS. Si tout se passe comme prévu, l'accostage aura lieu le 27 mai 2020. Il se déroulera en mode entièrement automatique, mais l'équipage pourra passer en commande manuelle. En fait, c'est le mode manuel qui est reproduit dans le simulateur.

Le simulateur lui-même est situé sur le site et est un jouet plutôt problématique pour la première crête ... La

navette spatiale essaie de voler dans le mauvais sens ... Et la précision avec laquelle vous devez entrer dans la passerelle est de 20 cm ... selon trois axes, ainsi que la vitesse angulaire, la vitesse de déplacement etc.

Des sentiments patriotiques ont commencé à jouer en moi et d'une certaine façon, c'est devenu une honte pour l'ancienne puissance spatiale, et j'ai pris ce simulateur comme un défi. Étant donné que Musk a décidé de montrer la complexité de l'amarrage et les difficultés rencontrées par leurs ingénieurs pour créer un programme d'amarrage automatique, j'ai décidé d'écrire, pendant mon temps libre, un programme JavaScript qui connecte facilement Dragon et l'ISS dans ce simulateur.

Ça vous plaît, Elon Musk?

image
Fumer est mauvais pour votre santé

Attention! Cet algorithme est "plaisanterie" et n'était pas destiné à être utilisé dans des conditions réelles. L'auteur n'est pas responsable des pertes directes ou indirectes subies par votre vaisseau spatial ou d'autres objets utilisant cet algorithme.




Tout d'abord, un peu d'histoire.

C'est un fait bien connu que notre navette spatiale réutilisable Buran était très similaire à une navette américaine. Et l'on sait aussi qu'il n'a volé qu'une seule fois, contrairement aux "homologues" américains. Mais peu de gens savent que son seul vol était sans pilote. Lui-même a décollé, il a atterri et il a fait tout cela dans de très mauvaises conditions météorologiques.

Les navettes américaines ont toujours atterri uniquement en mode manuel. Cela était surprenant étant donné que les ordinateurs n'étaient pas plus puissants qu'une calculatrice. Ça, en théorie, ça ne devrait pas être difficile, pensait hier soir.



Mais passons au point. Qu'est-ce qu'un simulateur sur le site Web de SpaceX .

Au début, nous voyons des informations générales selon lesquelles la déviation de tous les paramètres doit être inférieure à 0,2 mètre (20 cm). Compte tenu de la taille de la station et du navire, il s'agit d'une limitation assez sérieuse.

Nous démarrons le simulateur et voyons.



Au-dessus, à droite et en dessous du cercle central se trouve la déviation angulaire du navire sur trois axes.
Vert - valeur actuelle.

Bleu - la vitesse par seconde avec laquelle il change.

Décalage à gauche par rapport à la passerelle en mètres. Il n'y a pas de vitesse de déplacement ...

Les contrôleurs en bas de l'écran sont des boutons avec leur duplication sur le clavier.

Nous allons donc commencer par eux l'analyse du programme comme la moins intéressante.

La disposition des boutons du clavier.



Le bloc de gauche est responsable du décalage par rapport à la passerelle, mais celui de droite du décalage par rapport aux axes.

Nous écrivons, ou trouvons sur le réseau, un code qui peut émuler les clics du clavier sur le document. Dans mon cas, le code ressemblait à ceci.

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

Nous écrivons les codes des boutons:
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;

Tout système de contrôle implique un travail dans un cycle. Faisons-le le plus simple, par incréments de 200 millisecondes. Nous organiserons un compteur pour un, nous en aurons toujours besoin.

let index = 0;
function A() {

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

A();

Revenons à la structure du site.

Sa caractéristique intéressante est que l'ISS est dessiné sur toile, mais les informations sur l'état de notre vaisseau spatial sont dessinées avec le balisage habituel. On dirait que les développeurs du site ont supposé qu'il y aurait des passionnés similaires qui voudraient "automatiser" le jeu et leur donner une telle opportunité ... Ou peut-être qu'en le marquant, c'était stupide, plus facile à faire.



Et donc, ajoutons quelques lignes de lumière supplémentaires afin d'obtenir des informations sur l'état de notre vaisseau spatial.


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

Comme vous pouvez le voir, je n'ai pas tout retiré. Je n'ai retiré que les valeurs de décalage, mais je n'ai pas pris le taux de variation des valeurs, et voici pourquoi ...

En fait, c'est la troisième itération de l'algorithme. Au début, c'est une option simple, qui toutes les 200 millisecondes prend des informations sur l'état du navire et l'ajuste à 0. Il

ressemblait à ceci.

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

Et en fait, il était tout à fait un travailleur. Surtout pour les déplacements angulaires. Et pour le déplacement le long des axes, j'ai utilisé cette option.

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

La vitesse de déplacement du navire par rapport à chaque axe n'est pas affichée, mais elle n'est pas difficile à calculer.

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

Et cela s'est avéré assez tolérable. Circuit de commande de rétroaction classique. Et tandis que le navire était à distance de l'ISS, nous avons volé tout en douceur vers nous-mêmes [comme il me semblait alors]. Mais des problèmes ont commencé près du navire lui-même. En fait, le navire était très saucisson et il était physiquement impossible d'obtenir une précision de 0,2 mètre. Le fait est que le mélange de notre vaisseau a eu lieu ... disons dans un espace continu (avec une grande précision), mais nous n'en avons vu que des dixièmes. Et bien sûr, en essayant d'y répondre toutes les 200 millisecondes, nous avons obtenu des mesures réglementaires très strictes. Nous avons poussé les boutons trop souvent à la moindre déviation. Et plus le vaisseau se rapprochait, plus les valeurs de décalage commençaient à bondir et nous faisions encore plus basculer le vaisseau ...

Il fallait quelque part prendre la précision manquante. Dans la deuxième itération pour résoudre ce problème, j'ai moi-même essayé de calculer déjà uniquement sur la base du déplacement de vitesse. Et oui, cela semblait plutôt bien marcher aussi, mais cela n'a pas résolu le problème du mouvement ...

Quelle est l'essence du problème du mouvement? Eh bien, regardez, nous trouvons dans l'espace et en cliquant sur les boutons de commande, nous accélérons le vaisseau dans un avion ou un autre. Mais dès que l'on relâche le bouton, le mouvement ne s'arrête pas. Dans l'espace, en raison du vide, il n'y a pas de résistance. Et dès que nous avons donné une impulsion (en appuyant sur un bouton), le navire a commencé à se déplacer à cette vitesse ... Et il doit être arrêté d'une manière ou d'une autre. L'arrêt dans le simulateur est assez facile - vous devez donner une impulsion inverse.

Mais à la deuxième itération de la solution, la précision accrue de l'erreur ne m'a pas donné de réponse sur la façon d'ajuster la vitesse ...

Et ici nous avions besoin d'une «boussole». Le capitaine de tout navire / navire doit calculer l'itinéraire à l'avance. S'il donne l'ordre de ralentir après être entré dans le port, il est peu probable qu'il amarre les bijoux. Et nous en avons juste besoin.

Nous devons calculer l'itinéraire, les capitaines le font généralement avec une boussole avec un état discret de ses pointes. Et nous ferons de même. Nous calculerons l'itinéraire pour une seconde à l'avance, qui comprendra cinq itérations d'appuis sur les boutons ou non.

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

La fonction carculatePath , en fonction de leur valeur d'écart actuelle, calcule 5 étapes qui, en théorie, devraient réduire cet écart à 0. Pas nécessairement à cette itération, mais à chaque fois nous devons approcher le zéro précieux dans notre propre grille plus détaillée.

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

C'est tout, nous calculons l'itinéraire chaque seconde "égale" (index% 5 === 0) et maintenant il vous suffit de suivre ce cours.


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

Le seul calcul qui a survécu depuis la première itération est l'approche du navire.

C'est tout, d'accord, nous avançons à une vitesse relativement basse

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

Sous le spoiler se trouve le code complet. Vous pouvez vérifier vous-même ses performances sur le site Internet iss-sim.spacex.com

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


En fait, tout. Merci à tous d'avoir lu :)



PS


Oui, j'ai fait un peu d'amusement et je me suis moqué. Ne prenez pas l'article au sérieux, je voulais juste me sentir comme un astronaute [ou un flyer spatial], comme Gagarin Yuri Alekseevich ... Bien que ce soit probablement le travail d'ingénierie de Sergey Pavlovich Korolev , qui n'a malheureusement jamais visité l'espace dont il rêvait ...

All Articles