Docking the ISS using JavaScript and compass

The SpaceX company, founded by the notorious Ilon Mask, released a simulator for the manual docking of the Crew Dragon spacecraft with the ISS. If everything goes according to plan, the docking will take place on May 27, 2020. It will take place in fully automatic mode, but the crew will be able to switch to manual control. Actually, it is the manual mode that is reproduced in the simulator.

The simulator itself is located on the site and is a rather problematic toy for the first ridge ... The

space shuttle tries to fly the wrong way ... And the accuracy with which you need to get into the gateway is 20 cm ... along three axes, as well as angular velocity, displacement speed etc.

Patriotic feelings began to play in me and somehow it became a shame for the former space power, and I took this simulator as a challenge. Since Musk decided to show the complexity of the docking, and what difficulties their engineers went through to create an automatic docking program, I decided to write, in my free time, a JavaScript program that easily connects Dragon and the ISS in this simulator.

How do you like that, Elon Musk?

image
Smoking is bad for your health

Attention! This algorithm is "banter", and was not intended for use in real conditions. The author is not responsible for any direct or indirect losses incurred by your spacecraft or other objects using this algorithm.




First, a little history.

It is a well-known fact that our reusable space shuttle Buran was very similar to an American shuttle. And it’s also known that he flew only once, unlike the American “counterparts”. But few people know that his only flight was unmanned. He himself took off, he landed and he did all this in very bad weather conditions.

American Shuttles always landed only in manual mode. This was surprising given the fact that computers used to be no more powerful than a calculator. That, in theory, it should not be difficult, thought last night.



But let's get to the point. What is a simulator on the SpaceX website .

At the start, we see general information that the deviation in all parameters should be within 0.2 meters (20 cm). Given the size of the station and the ship, this is a pretty serious limitation.

We start the simulator and see.



Above, to the right and below the central circle is the angular deviation of the ship along three axes.
Green - current value.

Blue - the speed per second with which it changes.

Left offset relative to the gateway in meters. There is no displacement speed ...

The controllers at the bottom of the screen are buttons with their duplication on the keyboard.

So we’ll start with them the analysis of the program as the least interesting.

The layout of the keyboard buttons.



The left block is responsible for the offset relative to the gateway, but the right one for the offset relative to the axes.

We write, or find on the network, a code that can emulate keyboard clicks on document. In my case, the code looked like this.

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

We write the 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;

Any control system involves work in a cycle. Let's make it the easiest, in increments of 200 milliseconds. We’ll organize a counter for one, we’ll still need it.

let index = 0;
function A() {

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

A();

Let's go back to the structure of the site.

Its interesting feature is that the ISS is drawn on canvas, but the information about the state of our spacecraft is drawn with the usual markup. It feels like the developers of the site assumed that there would be similar enthusiasts who want to "automate" the game and give them such an opportunity ... Or maybe by marking it up, it was stupid, easier to do.



And so, let's add a couple more light lines in order to get information about the state of our spacecraft.


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

As you can see, I pulled out not everything. I pulled out only the offset values, but I didn’t take the rate of change of the values, and that's why ...

In fact, this is the third iteration of the algorithm. At first, it is a simple option, which every 200 milliseconds takes information about the condition of the ship and adjusts it to 0. It

looked like this.

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

And in fact, he was quite a worker. Especially for angular displacements. And for the displacement along the axes, I used this option.

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

The speed of the ship's displacement relative to each axis is not displayed, but it is not difficult to calculate.

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

And it turned out pretty tolerably. Classic feedback control circuit. And while the ship was at a distance from the ISS, we flew quite smoothly to ourselves [as it seemed to me then]. But problems began near the ship itself. In fact, the ship was very sausage and it was physically impossible to get an accuracy of 0.2 meters. The fact is that the mixing of our ship took place ... let's say in continuous space (with great accuracy), but we saw only tenths of it. And of course, trying to respond to them every 200 milliseconds, we got very strong regulatory actions. We have poked the buttons too many times at the slightest deviation. And the closer to the ship, the stronger the offset values ​​started to jump and we actually rocked the ship even more ... Increasing the amplitude of its movement ...

It was necessary somewhere to take the missing accuracy. In the second iteration in solving this problem, I myself tried to calculate already only on the basis of speed displacement. And yes, it seemed to work out pretty well, too, but this did not solve the problem with the movement ...

What is the essence of the problem of movement? Well, look, we find in outer space and by clicking on the control buttons we give acceleration to the ship in one plane or another. But as soon as we release the button, the movement does not stop. In outer space, due to the vacuum, there is no resistance. And as soon as we gave an impulse (by pressing a button) the ship began to move at this speed ... And it needs to be stopped somehow. Stopping in the simulator is quite easy - you need to give a reverse impulse.

But at the second iteration of the solution, the increased accuracy of the error did not give me an answer on how to adjust the speed ...

And here we needed a “compass”. The captain of any ship / vessel must calculate the route in advance. If he gives the command to slow down after he enters the port, then he is unlikely to moor jewelry. And we just need this.

We need to calculate the route, captains usually do this with a compass with a discrete state of its tips. And we will do the same. We will calculate the route for a second ahead, which will include five iterations of button presses or not.

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

The carculatePath function , based on their current deviation value, calculates 5 steps which in theory should reduce this deviation to 0. Not necessarily at this iteration, but each time we should approach the treasured zero in our own more detailed grid.

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

That's all, we calculate the route every "equal" second (index% 5 === 0) and now you just need to go this course.


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

The only calculation that has survived since the first iteration is the approach to the ship.

That's all, okay, we are moving forward at a relatively low speed

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

Under the spoiler is the full code. You can check its performance yourself on the website iss-sim.spacex.com

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


Actually everything. Thank you all for reading :)



PS


Yes, I did a little highpan and poked fun. Do not take the article seriously, I just wanted to feel like an astronaut [or space flyer], like Yuri Gagarin ... Although this is more likely the engineering work of Sergey Pavlovich Korolev , who unfortunately never visited the space he dreamed about ...

All Articles