Ancorando a ISS usando JavaScript e bússola

A empresa SpaceX, fundada pelo famoso Ilon Mask, lançou um simulador para o acoplamento manual da espaçonave Crew Dragon com a ISS. Se tudo correr conforme o planejado, o encaixe ocorrerá em 27 de maio de 2020. Isso acontecerá no modo totalmente automático, mas a equipe poderá mudar para o controle manual. Na verdade, é o modo manual que é reproduzido no simulador.

O simulador em si está localizado no local e é um brinquedo bastante problemático para a primeira cordilheira ... O

ônibus espacial tenta voar na direção errada ... E a precisão com a qual você precisa entrar no gateway é de 20 cm ... ao longo de três eixos, além de velocidade angular, velocidade de deslocamento etc.

Sentimentos patrióticos começaram a tocar em mim e, de alguma forma, tornou-se uma vergonha para o antigo poder espacial, e tomei esse simulador como um desafio. Como Musk decidiu mostrar a complexidade do encaixe e as dificuldades que seus engenheiros enfrentaram para criar um programa de encaixe automático, decidi escrever, no meu tempo livre, um programa JavaScript que conecta facilmente o Dragon e a ISS neste simulador.

Como você gosta disso, Elon Musk?

imagem
Fumar é ruim para sua saúde

Atenção! Esse algoritmo é "brincalhão" e não foi projetado para uso em condições reais. O autor não é responsável por quaisquer perdas diretas ou indiretas incorridas pela sua nave espacial ou por outros objetos usando este algoritmo.




Primeiro, um pouco de história.

É um fato bem conhecido que nosso ônibus espacial reutilizável Buran era muito semelhante a um ônibus espacial americano. E também se sabe que ele voou apenas uma vez, ao contrário dos "homólogos" americanos. Mas poucas pessoas sabem que seu único vôo não foi tripulado. Ele próprio decolou, aterrissou e fez tudo isso em condições climáticas muito ruins.

Os ônibus americanos sempre pousavam apenas no modo manual. Isso foi surpreendente, dado o fato de os computadores não serem mais poderosos que uma calculadora. Isso, em teoria, não deve ser difícil, pensou ontem à noite.



Mas vamos direto ao ponto. O que é um simulador no site da SpaceX .

No início, vemos informações gerais de que o desvio em todos os parâmetros deve estar dentro de 0,2 metros (20 cm). Dado o tamanho da estação e do navio, essa é uma limitação bastante séria.

Começamos o simulador e vemos.



Acima, à direita e abaixo do círculo central, está o desvio angular da nave ao longo de três eixos.
Verde - valor atual.

Azul - a velocidade por segundo com a qual muda.

Deslocamento à esquerda em relação ao gateway em metros. Não há velocidade de deslocamento ...

Os controladores na parte inferior da tela são botões com duplicação no teclado.

Então, começaremos com eles a análise do programa como a menos interessante.

O layout dos botões do teclado.



O bloco esquerdo é responsável pelo deslocamento em relação ao gateway, mas o bloco direito pelo deslocamento em relação aos eixos.

Escrevemos ou encontramos na rede um código que pode emular cliques no teclado no documento. No meu caso, o código ficou assim.

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

Escrevemos os códigos dos botões:
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;

Qualquer sistema de controle envolve trabalho em um ciclo. Vamos torná-lo o mais fácil, em incrementos de 200 milissegundos. Vamos organizar um balcão para um, ainda precisamos dele.

let index = 0;
function A() {

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

A();

Vamos voltar para a estrutura do site.

Sua característica interessante é que a ISS é desenhada sobre tela, mas as informações sobre o estado de nossa espaçonave são desenhadas com a marcação usual. Parece que os desenvolvedores do site presumiram que haveria entusiastas semelhantes que desejam "automatizar" o jogo e dar a eles uma oportunidade ... Ou talvez, ao marcar isso, fosse estúpido, mais fácil de fazer.



Então, vamos adicionar mais algumas linhas de luz para obter informações sobre o estado de nossa espaçonave.


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

Como você pode ver, tirei nem tudo. Peguei apenas os valores de deslocamento, mas não tomei a taxa de alteração dos valores, e aqui está o porquê ...

Na verdade, essa é a terceira iteração do algoritmo. A princípio, é uma opção simples, que a cada 200 milissegundos pega informações sobre as condições do navio e as ajusta a 0.

Parecia assim.

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

E, de fato, ele era bastante trabalhador. Especialmente para deslocamentos angulares. E para o deslocamento ao longo dos eixos, usei essa opção.

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

A velocidade do deslocamento do navio em relação a cada eixo não é exibida, mas não é difícil de calcular.

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

E acabou bem tolerável. Circuito de controle de feedback clássico. E enquanto o navio estava distante da ISS, voamos suavemente para nós mesmos [como me pareceu então]. Mas os problemas começaram perto do próprio navio. De fato, o navio era muito salsicha e era fisicamente impossível obter uma precisão de 0,2 metros. O fato é que a mistura de nossa nave ocorreu ... digamos em espaço contínuo (com grande precisão), mas vimos apenas décimos disso. E, é claro, tentando responder a eles a cada 200 milissegundos, temos ações regulatórias muito fortes. Apertamos os botões muitas vezes ao menor desvio. E quanto mais perto do navio, mais fortes os valores de deslocamento começaram a pular e nós realmente balançamos o navio ainda mais ... Aumentando a amplitude de seu movimento ...

Era necessário um lugar para obter a precisão que faltava. Na segunda iteração para resolver esse problema, eu próprio tentei calcular já apenas com base no deslocamento da velocidade. E sim, parecia funcionar muito bem também, mas isso não resolveu o problema do movimento ...

Qual é a essência do problema do movimento? Bem, olhamos, encontramos no espaço sideral e, clicando nos botões de controle, damos aceleração à nave em um plano ou outro. Mas assim que soltamos o botão, o movimento não para. No espaço sideral, devido ao vácuo, não há resistência. E assim que demos um impulso (com o apertar de um botão), o navio começou a se mover nessa velocidade ... E precisa ser parado de alguma forma. Parar no simulador é bastante fácil - você precisa dar um impulso reverso.

Mas na segunda iteração da solução, o aumento da precisão do erro não me deu uma resposta sobre como ajustar a velocidade ...

E aqui precisávamos de uma "bússola". O capitão de qualquer navio / embarcação deve calcular a rota com antecedência. Se ele der o comando de diminuir a velocidade depois de entrar no porto, é improvável que atracar jóias. E nós apenas precisamos disso.

Precisamos calcular a rota, os capitães costumam fazer isso com uma bússola com um estado discreto de suas dicas. E nós faremos o mesmo. Vamos calcular a rota por um segundo à frente, que incluirá cinco iterações de pressionamento de botão ou não.

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

A função carculatePath , com base em seu valor de desvio atual, calcula 5 etapas que, em teoria, devem reduzir esse desvio a 0. Não necessariamente nessa iteração, mas cada vez que devemos abordar o zero estimado em nossa grade mais detalhada.

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

Isso é tudo, calculamos a rota a cada segundo "igual" (índice% 5 === 0) e agora você só precisa seguir este curso.


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

O único cálculo que sobreviveu desde a primeira iteração é a abordagem ao navio.

Isso é tudo, ok, estamos avançando a uma velocidade relativamente baixa

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

Sob o spoiler está o código completo. Você pode verificar seu desempenho no site iss-sim.spacex.com

Código completo
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();


Na verdade tudo. Obrigado a todos pela leitura :)



PS


Sim, eu me diverti um pouco e zombei. Não leve o artigo a sério, eu só queria me sentir como um astronauta [ou flyer espacial], como Gagarin Yuri Alekseevich ... Embora isso seja mais provável que o trabalho de engenharia de Sergey Pavlovich Korolev , que infelizmente nunca tenha visitado o espaço com o qual sonhava ...

All Articles