使用JavaScript和指南针对接ISS

由臭名昭著的Ilon Mask创建的SpaceX公司发布了模拟器,用于将“乘龙”飞船与国际空间站进行手动对接。如果一切按计划进行,对接将于2020年5月27日进行。它将以全自动模式进行,但是机组人员将能够切换到手动控制。实际上,模拟器中复制的是手动模式。

模拟器本身就位于现场,对于第一个山脊来说,这是一个相当有问题的玩具...

航天飞机试图以错误的方式飞行...而且进入网关的精度为20 cm ...沿三个轴,以及角速度,位移速度等等

爱国的感觉开始在我体内发挥作用,并以某种方式使其成为前太空力量的耻辱,我将此模拟器视为挑战。由于Musk决定展示对接的复杂性,以及工程师创建自动对接程序遇到了哪些困难,我决定在业余时间编写一个JavaScript程序,该程序可以轻松地在此模拟器中连接Dragon和ISS。

埃隆·马斯克(Elon Musk),您觉得怎么样?

图片
吸烟有害健康

注意!该算法是“戏ter式”,并不适合在实际条件下使用。对于使用此算法的航天器或其他物体造成的任何直接或间接损失,作者概不负责。




首先,有一点历史。

众所周知的事实是,我们可重复使用的Buran航天飞机与美国航天飞机非常相似。而且众所周知,他只飞过一次,与美国的“同行”不同。但是很少有人知道他唯一的飞行是无人驾驶的。他本人起飞,着陆,并在非常恶劣的天气条件下进行了所有这些操作。

美式班车始终只能以手动模式降落。考虑到计算机过去没有计算器强大的功能,这令人惊讶。昨晚认为,从理论上讲,应该不难。



但是,让我们说清楚。什么是SpaceX网站上的模拟器

一开始,我们看到的一般信息是所有参数的偏差应在0.2米(20厘米)以内。考虑到车站和船只的大小,这是一个相当严重的限制。

我们启动模拟器,看看。



中心圆的上方,右侧和下方是船沿三个轴的角度偏差。
绿色-当前值。

蓝色-每秒变化的速度。

相对于网关的左偏移(以米为单位)。没有位移速度...

屏幕底部的控制器是按钮,它们在键盘上有重复。

因此,我们将以他们最不感兴趣的方式开始对程序进行分析。

键盘按钮的布局。



左边的块负责相对于网关的偏移,而右边的块负责相对于轴的偏移。

我们编写或在网络上找到可模拟键盘在文档上单击的代码。就我而言,代码看起来像这样。

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;

任何控制系统都涉及一个周期的工作。让我们最简单地以200毫秒为增量。我们将为一个组织一个柜台,我们仍然需要它。

let index = 0;
function A() {

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

A();

让我们回到站点的结构。

它的有趣特征是国际空间站是在画布上绘制的,但是有关我们飞船状态的信息是使用通常的标记绘制的。感觉好像网站的开发人员认为会有类似的发烧友想要“自动化”游戏并为他们提供这样的机会……或者通过标记它,它是愚蠢的,更容易实现。



因此,让我们添加多条灯光线,以获取有关航天器状态的信息。


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

如您所见,我没有抽出所有东西。我只提取了偏移量值,但没有计算值的变化率

,这就是原因。... 实际上,这是算法的第三次迭代。首先,这是一个简单的选项,每200毫秒获取有关船况的信息并将其调整为0。

看起来像这样。

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

实际上,他是一个工人。特别是对于角位移。对于沿轴的位移,我使用了此选项。

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

不会显示船舶相对于每个轴的位移速度,但这并不难计算。

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

结果证明是可以忍受的。经典的反馈控制电路。当这艘船离国际空间站很远时,我们就非常顺利地飞向了自己[就像当时对我来说]。但是问题开始于船本身附近。事实上,这艘船是非常香肠,物理上不可能达到0.2米的精度。事实是,我们的飞船是在连续空间中进行混合的(假设精度很高),但只看到了十分之一。当然,尝试每200毫秒响应一次,我们就采取了非常有力的监管措施。我们以最小的偏差戳了很多遍按钮。而且离船越近,偏移值就开始跳得越强,我们实际上使船晃动得更多...增加其运动幅度...

必须在某处采取缺失的准确性。在解决此问题的第二次迭代中,我本人试图仅根据速度位移进行计算。是的,它看起来也很不错,但这并不能解决机芯

的问题。机芯问题的本质是什么?好吧,看,我们在太空中找到了东西,通过单击控制按钮,我们可以在一艘或另一架飞机上为船加速。但是,只要我们松开按钮,运动就不会停止。在太空中,由于真空,没有阻力。当我们发出一声冲动(通过按下按钮)时,船便开始以这种速度运动……并且它需要以某种方式停止。停止在模拟器中非常容易-您需要给出反向冲动。

但是,在解决方案的第二次迭代中,错误的准确性提高了,但我没有给出关于如何调整速度的答案。在

这里,我们需要一个“指南针”。任何船舶的船长都必须提前计算路线。如果他在进入港口后发出命令减速,那么他就不太可能会停泊珠宝。我们只需要这个。

我们需要计算路线,机长通常使用罗盘,而罗盘的尖端处于离散状态。我们将做同样的事情。我们将计算前进一秒钟的路线,这将包括是否进行五次按键迭代。

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

carculatePath 函数基于其当前偏差值,计算出5个步骤,理论上应将该偏差减小为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;
}

就是这样,我们每隔“相等”秒(索引%5 === 0)计算路线,现在您只需要执行此过程即可。


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

扰流器下方是完整代码。您可以在iss-sim.spacex.com网站上自行检查其性能

完整代码
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();


其实一切。谢谢大家阅读:)



聚苯乙烯


是的,我做了一点高跟和玩笑。不要把文章认真,我只是想感觉像一个宇航员[或空间飞行器],像加加林尤里·阿列克谢耶维奇 ......虽然这是更可能的工程工作谢尔盖·科罗廖夫洛维奇,谁不幸从未访问过他梦想的空间...

All Articles