Стыкуемся с МКС с помощью JavaScript и циркуля

    Компания SpaceX, основанная небезызвестным Илоном Маском, выпустила симулятор ручной стыковки корабля Crew Dragon с МКС. Если все пойдет по плану, стыковку проведут 27 мая 2020 года. Она будет проходить в полностью автоматическом режиме, но экипаж корабля сможет переключиться на ручное управление. Собственно, именно ручной режим и воспроизведен в симуляторе.

    Сам симулятор расположен на сайте и представляет собой, довольно проблематичную, на первый взгряд игрушку…

    Космический челнок так и норовит улететь не туда… А точность с которой нужно попасть в шлюз составляет 20 см… по трем осям, а также по угловой скорости, скорости смещения и т.д.

    Во мне заиграли патриотичные чувства и как-то стало обидно, за бывшую космическую державу, и я принял этот симулятор как вызов. Раз Маск решил показать сложность стыковки, и какие сложности их инженеры проходили, чтобы сделать программу автоматической стыковки, я решил написать, в свободное от работы время, программу на JavaScript, которая с легкостью состыкует Dragon и МКС в этом симуляторе.

    Как тебе такое, Илон Маск?

    image
    Курение вредит вашему здоровью

    Внимание! Данный алгоритм является «стебным», и не предназначал для использования его в реальных условиях. Автор не несет ответственности за любые прямые или косвенные убытки, нанесенные вашему космическому кораблю или иным объектам при использовании данного алгоритма.




    Для начала немного истории.

    Широко известный факт, что наш многоразовый космический корабль «Буран» был очень похож на американский челнок. А так же известно, что он летал всего только один раз, в отличии от американских «аналогов». Но мало кто знает, что его единственный полет был беспилотным. Он сам взлетел, сам приземлился и все это он сделал в очень плохие погодные условия.

    Американские Шатлы всегда приземлялись только в ручном режиме. Это было удивительно, принимая во внимание тот факт, что компьютеры раньше были не мощнее калькулятора. То, в теории, это не должно быть сложно, — подумал вчера вечером.



    Но дайте перейдем к сути. Что представляет собой симулятор на сайте SpaceX.

    На старте мы видим общую информацию, что отклонение по всем параметрам должно быть в пределах 0.2 метра (20 см). Учитывая размеры станции и корабля, это довольно серьезное ограничение.

    Запускаем симулятор и видим.



    Сверху, справа и снизу центральной окружности — угловое отклонение корабля по трем осям.
    Зеленым — текущее значение.

    Синие — скорость в секунду с которым оно изменяется.

    Слева смещение относительно, шлюза в метрах. Скорости смещения нет…

    Контролёры управления внизу экрана представляют собой кнопки с дублированием их на клавиатуре.

    Вот с них и начнем разбор программы, как наименее интересных.

    Схема клавиатурных кнопок.



    Левый блок отвечает за смещение относительно шлюза, а вот правый за смещение относительно осей.

    Пишем, или находим в сети, код который умеет эмулировать клавиатруные нажатие на document. В моем случае код выглядел так.

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

    Собственно всё, мы рассчитываем маршрут каждую «равную» секунду (index % 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();


    Собственно всё. Всем спасибо за прочтение :)



    P.S.


    Да, я немного хайпанул и поприкалывался. Не воспринимайте статью всерьез, я просто хотел почувствовать себя космонавтом [или летуном космическим], как Гагарин Юрий Алексеевич… Хотя это скорее инженерная работа Королёва Сергея Павловича, который к сожалению, так и не побывал в космосе, о котором мечтал…
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 96

      +10
      И естественно, пытаясь реагировать на них каждые 200 миллисекунд, у нас получалось очень сильные действия по регулированию. Мы слишком много раз «тыкали на кнопки» при малейшем отклонении. А чем ближе к кораблю, тем сильнее значения смещения начинали скакать и мы фактически еще больше раскачивали корабль…

      Но ведь ПИД же есть. :)
        +14
        Да не только ПИД, а в принципе хорошо разработанная теория автоматического управления. Для систем с обратной связью надо обеспечивать стабильность, что, по-хорошему требует некоторых теоретических выкладок и построения диаграмм Боде. Если все нормально посчитать, то никакого ухода в автоколебания не будет — и это именно такая задача, которую должна решать настоящая система автоматической стыковки.
        +21
        Сам симулятор расположен на сайте сайте и представляет собой, довольно проблематичную, на первый взгряд игрушку…
        Ничего сложного в игрушке нет. Выводишь угловые отклонения в ноль и действуя только смещениями наводишься на центр шлюза. Даёшь импульс вперёд и короткими боковыми импульсами удерживаешь центр. Перед самой стыковкой тормозишь до минимума.
        В общем, после стыковок в KSP, особенно когда смещён центр масс и цель тоже слегка поворачивается, эта игрушка проходится на ура с первого раза.
          0

          Хм… Могу только порадоваться за Вас:) Ибо я сначала пробовал только по визуальным ориентирам проходить, но безуспешно. До шлюза доходил и получал провал, не выполнив одно из условий.

            +11
            Зачем по визуальным? Игра показывает угловые отклонения и угловые скорости, боковые отклонения и расстояние. Несколько не хватает индикации боковых скоростей, но тут уже проще.
            Главное — не спешить, и изначально заходить максимально соосно, с нулевым поворотом и скоростями по всем трём осям.
            +1
            Навыки входа в станцию без посадочного компьютера в Elite тоже способствуют…
            ru.wikipedia.org/wiki/Elite
              0
              Ну фиг знает, как по мне в Элите куда проще было. Тут инерционность куда выше.
            +2
            На изображении клавиатурного управления небольшая ошибка — в левом блоке кнопка, отвечающая за смещение вправо, это «D», а не «F».
              0
              Спасибо. Внес правки.
              +2
              Мне пришлось заменить outerText на textContent
                +4
                Хм… Ну в принципе, да, можно и так. Вообще я писал свой код под Chrome и не стал проверят, в других браузерах.:)
                Удивил факт, что было сложно найти код эмуляции кнопок, ибо даже заплюсованные ответы на StackOverflow почему-то не работали.
                  –12
                  Плюсанул бы. Но карму ухекал :)
                    0
                    Помимо самих кнопок генерируется довольно много разных событий, которые могут требоваться для используемоего фреймворка.
                  +10
                  Американские Шатлы всегда приземлялись только в ручном режиме. Это было удивительно, принимая во внимание тот факт, что компьютеры раньше были не мощнее калькулятора.

                  Спейс Шатлы садились в автоматическом режиме до высоты примерно в 15 тысяч метров и пилот брал управление на себя, когда скорость становилась уже дозвуковая, когда до посадки оставалась пару минут. Полная автоматическая посадка была доступна до касания ВПП и испытывалась в одной из первых миссий еще в 82-ом году. И то, что она была возможно только до касания ВПП, — было не техническим, а концептуальным ограничением. Невозможность посадки челнока без человека внутри было требованием пилотов при разработке: им хотелось хоть немного порулить, они ведь военные пилоты, а тут их хотят какой-то машиной заменить :)
                  Так что Буран здесь не совершил никакого прорыва.
                    +7
                    Про Шаттл — очень живучий миф. Отсекается даже без знания фактологии, если знать когда была первая автоматическая посадка, и представлять, насколько сложно свести планер с орбиты с точностью достаточной для посадки на полосу с единственной попытки (человеку такое никогда бы не доверили). А еще ведь стабилизация есть на горячем участке, тоже та еще задачка.

                    Материалов вот маловато на тему того, что после Колумбии удалось заавтоматизировать на Шаттле тем самым проводом, а то таки мешало(?) нормальной посадке со всеми процедурами.
                      +1
                      Материалов вот маловато на тему того, что после Колумбии удалось заавтоматизировать на Шаттле тем самым проводом, а то таки мешало(?) нормальной посадке со всеми процедурами.
                      Материалов вполне хватает (если владеть English), а «что мешало» — как ни странно, пресловутый «человеческий фактор».

                      В отличие от советской космонавтики, где космонавты не очень-то отличались от Белки и Стрелки (или чукчи из широко известного анекдота), американские программы всегда ориентировались на человека-пилота.
                        0

                        Поделитесь линком. Везде remote control capabilities без подробностей, т.е. не ясно, старт ВСУ, выпуск атмосферных сенсоров и т.д. выполнялись полностью автоматически или по команде с Земли. Костыль всё-таки, да и писали бы прямым текстом про автономность, но вот нет.

                          –1
                          Сорри, сейчас не могу поделиться; когда-то находил (упорно гуглил и искал на сайте NASA), но впервые увидел описание автоматики на экспозиции шаттла Atlantis, в выставочном центре на мысе Канаверал.
                          +1
                          Ну, это Вы зря.
                          Я п присоветовал с вопросом-то разобраться.
                          Очень много ручных операций на орбите. НАчиная от ручных стыковок (бывают) до перестыковки кораблей от одного стыковочного узла на другой. Вручную…
                          Очень высокая подготовка космонавтов, именно как пилотов кораблей. И не у всех, кстати, получается — очень специфичная техника…
                          Ну и кучи аварийных ситуаций, где пристыковывались из, казалось, невозможных ситуаций менно вручную.
                          И под Буран был готов целый отряд, «Волчья стая», куда были набраны высококлассные лётчики-испытатели…
                          Так что про белок-стрелок-чукч, это вы мимо… уж извините. Это, если вкратце…
                          А насчёт автоматики — так этоизначально так должно быть. Мало ли что с космонавтом. Он должен быть жив-здоров, пристыкован/отстыкован и вернут на Землю… Такая практика шла изначально. И у нас, и у них…
                            –1
                            Farik196, не поймите меня неправильно: я отнюдь не отрицаю высокую профессиональную подготовку советских и российских космонавтов, а также их исключительно высокие качества (за исключением тех моментов, когда некоторые из них вещают с трибуны ГосДуры о лично наблюдаемых с орбиты «жыдобандеровцах, обстреливающих мирное население Донбасса). Отсылаю Вас к великолепному труду Бориса Чертока „Ракеты и люди“ — там эта проблема рассмотрена и объяснена (с точки зрения тогдашней „правды“) самим творцом всей советской космической автоматики.
                              0
                              Абсолютно не хочу понять Вас неправильно, но, пожалуй, Вы валите всё в одну кучу и космонавтику и «жыдобандеровцев». Даже не хочу про это.
                              ЗЫ: Бориса Евсеевича имел честь хоть чуть-чуть, но знать лично. Книга его перечитана не раз.
                              С уважением.
                                0
                                Я не «валю в кучу», а просто к слову пришлось; я бы тоже не хотел, но это, к сожалению, факт (небольшой штрих касательно высокой подготовки советских/российских космонавтов).
                        +1

                        Да и фраза "компьютеры раньше были не мощнее калькулятора" мягко говоря смущает. Из вики:


                        Бортовой комплекс управления состоит примерно из пятидесяти программных систем, на базе компьютера IBM System/370. Часть системных команд из IBM-набора S/370 не была реализована, в то же время было добавлено много оригинальных команд общего назначения, не имеющих аналогов в IBM-наборе. На борту корабля находилось два комплекта БЦВМ «Бисер-4» по четыре аппаратно-параллельных компьютера и аппаратного компаратора, допускающего автоматическое отключение подряд двух компьютеров в случае аварийных результатов (4 основных + 4 резерв).

                        Я так думаю, что этот "набор" с десяток пилотов (работающих слаженно) заменил. Поэтому преподносимый автопилот как какое-то достижение по мне как раз наоборот.

                          0
                          Насколько я помню из книги американского космонавта Майка Маллейна, система автоматической посадки таки была, но ей ни разу не воспользовались по указаным Вами причинам.
                            0

                            Не помню уже источник, но очень нравится это высказывание (своими словами): "… в СССР космос осваивали ракетчики, а в Штатах — пилоты самолетов"

                            +3
                            Под спойлером полный код. Вы можете сами проверить его работоспособность на сайте iss-sim.spacex.com

                            Инструкция б не помешала бы. Заодно может организовать соревнования по наилучшему (самому быстрому, самому экономному) алгоритму автоматической стыковки?
                            Условный расход топлива можно считать по количеству импульсов и их длительности.
                              +2
                              Инструкция б не помешала бы.

                              1) Открыть сайт и запустить эмулятор.
                              2) Открыть консоль F12
                              3) Открыть вкладку Console
                              4) Вставить код из под «Спойлера». Нажать Enter. :)
                              5) Наслаждаться. :)
                              Заодно может организовать соревнования по наилучшему (самому быстрому, самому экономному) алгоритму автоматической стыковки?

                              Я только ЗА! :)
                              Но я не у верен, что найдутся еще энтузиасты.
                                0
                                Я бы поучаствовал, только я питонист. Вот думаю, как реализовывать получение данных.
                                  0
                                  Можно взять мой код и выкинуть из него сам алгоритм. Получение данных и функцию клика можете оставить и сотворить свой вариант.:)
                                0
                                Идея хорошая. Только там нет длительности импульсов. Можно будет считать только количество.
                                  0

                                  Экономный будет довольно долгим, но около 20 импульсов хватит: 6 на выравнивание по курсу/крену/тангажу, 4 на грубое выравнивание осей, остальное — на разгон/торможение и тонкое выравнивание. Главное — не пытаться постоянно держать строго нули, позволить немного "гулять", окончательно свести оси лишь непосредственно перед касанием парой-другой импульсов.

                                  0
                                  это что он курить такое?)))
                                  +1
                                  Что я делаю не так?
                                  Скрытый текст
                                  image
                                    0
                                    Хм… Вообще из коробки должно работать. Повторятся часто?
                                      0
                                      то же самое
                                        0
                                        Возможно поможет совет
                                        Мне пришлось заменить outerText на textContent
                                        +1
                                        Вообще конечно больно смотреть на этот способ регулирования :) реализовали бы хотя бы ПИД, как советовали выше — было бы намного быстрее и проще.
                                          0
                                          У меня аналогично. Из двух попыток — два провала. Rotation Speed — как и на этом скриншоте.

                                          PS: браузер — Firefox. Пришлось заменить outerText на textContent — как советовали выше.
                                            0
                                            Поменял в статье outerText на textContent и проверил на FireFox.
                                            У меня вышло успешно. У Вас не получилось?
                                              0
                                              Сначала скрипт не стартовал — ругался на ошибку. После замены outerText на textContent — перестал ругаться и начал работать. Но стыковка завершилась неудачей. Пробовал два раза — оба раза фейл с выдачей сообщения «Rotation Speed».
                                          0
                                          Собственно ваша вторая итерация — как я понимаю — попытка привнести дифференцирующее звено в ваш контур управления, поэтому стало работать лучше :) но вы пытаетесь оценить скорость движения странными дискретными кусками по секунде.
                                            0
                                            Это похоже на Arduino стайл :-)
                                              0
                                              В финале, я фактически забил на скорость изменения, а лишь пытаюсь привести систему в состояние равное 0. Дифференцирующее звено — даст нам скорость изменения (если я не ошибаюсь). Конечно, в определенной степени, за это показатель, отвечает поле «Speed». Но основная суть — просчитать поведение и давать такое управляющее воздействие, дабы стабилизировать все в 0.

                                              По Поводу ПИД — в ваших руках сделать лучше) Я не против посоревноваться. Мне кажется, проблема в очень дискредитизированных значениях, которые мы считываем… Они не только ведь дискретны, но и еще идут с определённой задержкой от управляющего воздействия. Но, как я уже сказал, я готов посмотреть и возможно даже посоревноваться с Вашим решением.
                                            +6
                                            Ваш вариант ещё надо очень дорабатывать. Слишком медленно и нестабильно.

                                            Стыковка по-ковбойски (судя по всему люди, а не скрипты):
                                            27 секунд


                                            23 секунды


                                            Кто уложится в 20 секунд?
                                              0
                                              Стыковка по-ковбойски

                                              Можете пояснить?
                                                +2
                                                Это просто примеры «быстрого прохождения» игры. Где-то в комментариях их сравнили с ковбоями.
                                                  0
                                                  Видимо в том смысле что весело, задорно и с упором на свое мастерство вместо безопасности стыковки?
                                                  0
                                                  такое вообще возможно?
                                                    0
                                                    Я думаю, что с помощью NES speedrun-а это вполне возможно. :)
                                                      0
                                                      На первом ролике — ювелирная работа. Прямо в яблочко! Второй — так себе — на грани фола. Сумел дёрнуться в самом конце и за счёт этого попал. Можно сказать — случайность.
                                                        0
                                                        за 27 секунда человек не сможет. Скриптом даже сложно такое время сделать.
                                                        +4
                                                        В настройках игры порадовала опция «Плоская Земля»
                                                          +6
                                                          Там еще пасхалка есть. С обратной стороны, машина, которую Маск в космос запустил.
                                                            0
                                                            Обьясните новичку, а куда вставлять этот код, чтоб работал?
                                                              0
                                                              Я чуть выше писал инструкцию.
                                                              1) Открыть сайт и запустить эмулятор.
                                                              2) Открыть консоль F12
                                                              3) Открыть вкладку Console
                                                              4) Вставить код из под «Спойлера». Нажать Enter. :)
                                                              5) Наслаждаться. :)
                                                          0
                                                          Хотя это скорее инженерная работа Королёва Сергея Павловича, который к сожалению, так и не побывал в космосе, о котором мечтал…


                                                          Ну, баллистика-то скорее работа не С.П.Королева, а все-таки баллистиков. Например, достаточно известен один из руководителей отдела Энергии Р.Ф. Аппазов, про которого даже страница в Википедии есть, и который эту самую баллистику нам читал в свое время.
                                                            0

                                                            Простите, немного не по теме, но давно мучает вопрос: почему нельзя в космосе на определенном моменте сближения пробросить с одного корабля на другой трос и потом просто притянуть один корабль к другому. Мне кажется, автоматическую систему бросания/ловления троса реализовать совсем просто. А если стягивать по оси стыковочных узлов, то корабли автоматически центрируются.

                                                              0
                                                              Я полагаю, что проблема в отсутствии сопротивления. Космические корабли — не морские суда.
                                                              Вы притягиваете трос и начинаете двигаться к станции. Во-первых Вы двигаете еще и станцию, меняя ее орбиту. Во вторых вы начали двигаться в направлении станции, как тормозить?
                                                                +1

                                                                Потому что космос — не вода. Если корабль смотрит носом не точно на лебёдку и если трос прикреплен к носу корабля, то при первоначальном рывке троса корабль начнет вращаться и остановить вращение можно будет только двигателями.


                                                                Да и вообще, как вы представляете перебрасывание троса? Это надо буквально выстрелить им из станции и попасть в какой-то приемный порт корабля. Учитывая, что корабль тоже движется и вращается. И что там снаружи торчат хрупкие антенны и солнечные батареи.


                                                                Проще уж воспользоваться канадармом, но он есть только на МКС. Т.е. это — не универсальное решение.

                                                                  0
                                                                  Трос создаст вращающий момент, который необходимо будет компенсировать. Вряд ли тянущее усилие будет проходить строго через центры масс обоих аппаратов.
                                                                  Ну и кроме соосности в стыковочном узле необходимо ещё совпадение угла поворота, иначе не сработают сцепные механизмы шлюза.
                                                                    +4
                                                                    Если честно то и сейчас американские грузовые корабли просто сближаются и их захватывает Сanadarm(аналог троса) и точно притягивает последние 5-10 метров
                                                                    –2
                                                                    Прикольная штука.
                                                                      +4
                                                                       Эх, салаги! :)
                                                                      image
                                                                        0
                                                                        Кто в элиту играл тот Маска не боиться. Я тоже первым делом подумал о стыковке в первой элите )
                                                                          +3
                                                                          Я первый раз в жизни к станции стыковался вот так

                                                                          0
                                                                          Можно не трогать DOM а использовать их собственный API: camera.rotation.x/.y/.z — pitch/yaw/roll, issObject.position.x — camera.position.x (/y/z) — координаты, ну и управлять translateRight/yawRight и проч. У меня с копипастами вышло полтора килобайта, думаю можно уложиться в твит при желании.
                                                                            +1
                                                                            Выложи код сюда.
                                                                              0
                                                                              function sr(n,rr,l,m){
                                                                                  var d = camera.rotation[n]
                                                                                  if(Math.abs(d)<0.002)
                                                                                      return
                                                                              
                                                                                  tr = d/5
                                                                                  r = rr()/10*toRAD
                                                                                  if(r > tr)
                                                                                      l()
                                                                                  else
                                                                                      m()
                                                                              }
                                                                              
                                                                              function sp(n,l,m) {
                                                                                  var d = issObject.position[n] - camera.position[n]
                                                                                  if(Math.abs(d) < 0.07)
                                                                                      return
                                                                              
                                                                                  tr = d * 0.01
                                                                                  cr = motionVector[n]
                                                                              
                                                                                  if(cr > tr) {
                                                                                      l()
                                                                                  } else {
                                                                                      m()
                                                                                  }
                                                                              }
                                                                              
                                                                              function setZ() {
                                                                                  if(Math.abs(issObject.position.x - camera.position.x) > 0.2
                                                                                      || Math.abs(issObject.position.y - camera.position.y > 0.2) ) {
                                                                                          return
                                                                                      }
                                                                              
                                                                                  var d = issObject.position.z - camera.position.z
                                                                                  var tr = Math.max(0.002 + d*d/20000, 0.1)
                                                                                  var cr = -motionVector.z
                                                                              
                                                                                  if(cr < tr) {
                                                                                      translateForward()
                                                                                  } else {
                                                                                      translateBackward()
                                                                                  }
                                                                              }
                                                                              
                                                                              function zeroIn() {
                                                                                  sr('y', ()=>rateRotationY,yawLeft,yawRight)
                                                                                  sr('x',()=>rateRotationX,pitchUp,pitchDown)
                                                                                  sr('z',()=>rateRotationZ,rollLeft,rollRight)
                                                                                  sp('y', translateDown, translateUp)
                                                                                  sp('x', translateLeft, translateRight)
                                                                                  setZ();
                                                                              }
                                                                              
                                                                              window.clearInterval(iii)
                                                                              var iii = window.setInterval(zeroIn, 200)
                                                                              
                                                                              
                                                                                0
                                                                                Упс, поменяйте Math.max(0.002 + d*d/20000, 0.1) на Math.min, а то оно будет стыковаться на полной скорости :-)
                                                                                  0
                                                                                  Поробовал два раза. Оба раза провальные из-за скорости (при min)



                                                                                  Но вообще, идет к цели хорошо…
                                                                                    0
                                                                                    А на какой скорости оно стыкуется? У меня оно за 2 минуты подходит на 10 метров, а потом мееедленно крадется на 0.03m/s
                                                                                      0
                                                                                      Сначала долго стояли и выравнивались… А потом просто разогнались до довольно внушительной скорости и врезались на ней.
                                                                                        0
                                                                                        Очень похоже на Math.min -> Math.max. Сделайте page refresh, и запустите правильный код — должно заработать. Правда, будет мучительно медленно, особенно на последних метрах :-)
                                                                                          0
                                                                                          За «полсекунды» до удара.
                                                                                            0
                                                                                            Ага, надо поиграть с константой максимальной скорости, и вообще похоже что в Хроме оно резче реагирует на элементы управления — корабль разворачивает гораздо бодрее. Может, от производительности браузера зависит?

                                                                                            Запостил сюда, кстати github.com/vkalinsky/spacexiss-autodocker
                                                                                              0
                                                                                              Может, от производительности браузера зависит?/blockquote>
                                                                                              Ну что-то в этом точно есть. Так что, не мой алгоритм «медленный» просто комп тормозит :D
                                                                                    0
                                                                                    С «camera» хак интересный… Я о таком не знал…
                                                                                    Хотя… Получается это чуть-чуть, в обход «обозначеных условий» задачи. Вопрос уже не к решению, а к постановке. Но пусть каждый решает сам для себя насколько допустимо такое.
                                                                              +2
                                                                              Штука прикольная, но у меня скрипт не смог пристыковаться. (((
                                                                              Плюс очень уж много манипуляций происходит. Если подсчитать расход топлива, то совсем неэффективно получается.
                                                                                +1
                                                                                Пилот «принял на грудь». Идея классная, но реализация — дрянь. Код примитивный и работает очень плохо. Особенно убило, что умудряется провалить стыковку, даже когда уже полтора метра до шлюза осталось и можно просто откинуться в кресле.
                                                                                  0
                                                                                  Вот все жду, когда «высококлассные программисты», коих полно на Хабре, выдадут на гора решение этой простой задачи из области управления с обратной связью, да чего-то уже два дня прошло, а решения все нет и нет.

                                                                                  Походу все-таки придется мне сесть и написать свое решение…
                                                                                    0
                                                                                    Вперёд. Сделаете решение лучше или проще, то вопросов нет. Я уже сказал, что буду рад посоревноваться. И, может, даже улучшить свой код.
                                                                                    Я учитывая, что мое решение, которое я писал в свободное после работы время пару вечеров назвали «дрянью», очень жду решения от вас.
                                                                                      0
                                                                                      Не, вопросы есть.
                                                                                      Я могу сделать решение на основе релейной логики или ТАУ с описанием модели КК в виде интеграторов. Но это же классика!
                                                                                      Где ИИ? Где нейросети и машинное обучение? Вот это я ожидал увидеть, а не джаваскрипт, который даже не может пристыковаться с первого раза. Решение задачи в виде «я скормил параметры yaw, pitch и speed моему пет-проектному ИИ фреймворку, который выдал мне команды на нажатие кнопок и все заработало» — где оно?

                                                                                        0
                                                                                        Да хоть как-нибудь сделайте, а потом говорите, что мое решение — дрянь.
                                                                                        0
                                                                                        Код получился сложнее, но работает гораздо лучше.
                                                                                        m = {

                                                                                        sleep: function (ms) {
                                                                                        return new Promise(resolve => setTimeout(resolve, ms));
                                                                                        },

                                                                                        round: function (f, digits = 0) {
                                                                                        if (digits) {
                                                                                        let multiplier = Math.pow(10, digits);
                                                                                        return Math.round((f + Number.EPSILON) * multiplier) / multiplier;
                                                                                        } else {
                                                                                        return Math.round(f);
                                                                                        }
                                                                                        },

                                                                                        press: async function (code, cnt = 1) {
                                                                                        let lookup_tbl = {
                                                                                        'e': 69, 'q': 81, 'a': 65, 'd': 68, 'w': 87, 's': 83,
                                                                                        'left': 37, 'up': 38, 'right': 39, 'down': 40,
                                                                                        '<': 103, '>': 105
                                                                                        };

                                                                                        let e1 = new Event('keydown');
                                                                                        let e2 = new Event('keyup');
                                                                                        e1.keyCode = lookup_tbl[code];
                                                                                        e2.keyCode = lookup_tbl[code];
                                                                                        //кол-во повторений
                                                                                        while (cnt--) {
                                                                                        document.dispatchEvent(e1);
                                                                                        document.dispatchEvent(e2);
                                                                                        await this.sleep(1);
                                                                                        }
                                                                                        },

                                                                                        get_state: function (s) {
                                                                                        let o = window.document.getElementById(s);
                                                                                        let v1 = o.children[0].innerHTML;
                                                                                        let v2 = o.children[1].innerHTML.split(' ')[0];
                                                                                        v1 = parseFloat(v1);
                                                                                        v2 = parseFloat(v2);
                                                                                        return {'offset': v1, 'speed': v2, 'offset_abs': Math.abs(v1), 'speed_abs': Math.abs(v2)}
                                                                                        },

                                                                                        get_xyz: function () {
                                                                                        let x = window.document.getElementById('x-range');
                                                                                        let y = window.document.getElementById('y-range');
                                                                                        let z = window.document.getElementById('z-range');
                                                                                        x = x.children[0].innerHTML.split(' ')[0];
                                                                                        x = parseFloat(x);

                                                                                        y = y.children[0].innerHTML.split(' ')[0];
                                                                                        y = parseFloat(y);

                                                                                        z = z.children[0].innerHTML.split(' ')[0];
                                                                                        z = parseFloat(z);

                                                                                        return {'x': x, 'y': y, 'z': z, 'x_abs': Math.abs(x), 'y_abs': Math.abs(y), 'z_abs': Math.abs(z)}
                                                                                        },
                                                                                        /*
                                                                                        логика такая: включаем наблюдателя, ловим изменение значение и замеряем скорость,
                                                                                        ловим 2 раз и определяем скорость, отключаем наблюдателя и возвращаем результат
                                                                                        в промис.
                                                                                        */
                                                                                        calc_pos: async function (axis) {
                                                                                        let el = window.document.getElementById(axis + '-range').children[0];
                                                                                        let t1, t2, tt1, tt2, v1, v2, ms, speed, counter = 0;

                                                                                        //отслеживать изменения значений будем через наблюдателя
                                                                                        //callback функция для наблюдателя
                                                                                        return new Promise(resolve => {

                                                                                        let callback = (mutationsList, observer) => {
                                                                                        let m = mutationsList[0];
                                                                                        let ischanged;
                                                                                        //если у нас уже есть t1
                                                                                        //отловим изменение скорости на 2 единицы, чтобы
                                                                                        //вычисления были точнее
                                                                                        //ловить будем только, если что-то поменялось
                                                                                        try {
                                                                                        ischanged = m.addedNodes[0].nodeValue !== m.removedNodes[0].nodeValue;
                                                                                        } catch (e) {
                                                                                        //pass
                                                                                        }

                                                                                        if (ischanged) {
                                                                                        let islowspeed = t1 && (performance.now() - t1) >= 1300;
                                                                                        if (counter == 40 || islowspeed){
                                                                                        t2 = performance.now();
                                                                                        v2 = m.addedNodes[0].nodeValue;
                                                                                        v2 = parseFloat(v2.split(' ')[0]);
                                                                                        ms = t2 - t1;
                                                                                        speed = (v1 - v2) * 1000 / ms;
                                                                                        //вырубаем наблюдателя
                                                                                        observer.disconnect();
                                                                                        resolve(this.round(speed, 2));
                                                                                        } else {
                                                                                        //замер делаем только на первом цикле
                                                                                        if (!counter) {
                                                                                        t1 = performance.now();
                                                                                        v1 = m.addedNodes[0].nodeValue;
                                                                                        v1 = parseFloat(v1.split(' ')[0]);
                                                                                        }
                                                                                        //увеличиваем счетчик циклов
                                                                                        ++counter;
                                                                                        }
                                                                                        }
                                                                                        //на случай, если скорость не изменяется в течение 5 секунд
                                                                                        if (!tt1) {
                                                                                        tt1 = performance.now();
                                                                                        //если не было ни одного изменения
                                                                                        } else if (!counter) {
                                                                                        tt2 = performance.now() - tt1;
                                                                                        if (tt2 > 5000) {
                                                                                        //вырубаем наблюдателя и возвращаем 0
                                                                                        observer.disconnect();
                                                                                        resolve(0);
                                                                                        }
                                                                                        }
                                                                                        };

                                                                                        let observer = new MutationObserver(callback);

                                                                                        observer.observe(el, {
                                                                                        characterData: false,
                                                                                        childList: true,
                                                                                        attributes: false
                                                                                        });
                                                                                        });

                                                                                        },

                                                                                        //возвращает нужный ключ массива и его порядковый номер
                                                                                        get_idx: function (a, val, column = 0) {
                                                                                        if (isNaN(val)) {
                                                                                        return null;
                                                                                        }
                                                                                        for (let i = 0; i < a.length; ++i) {
                                                                                        //если значение меньше ключа, значит мы у цели
                                                                                        if (a[i][column] > val) {
                                                                                        //предыдущий индекс(если есть) и есть искомый ключ
                                                                                        i = i > 0 ? i - 1 : i;
                                                                                        return i;
                                                                                        }
                                                                                        }
                                                                                        //если ничего лучше нет, отдаем последний индекс
                                                                                        return a.length - 1;
                                                                                        },

                                                                                        flip_obj: function (table) {
                                                                                        res = {}
                                                                                        let keys = Object.keys(table);
                                                                                        for (let i = 0, len = keys.length; i < len; ++i) {
                                                                                        let key = keys[i];
                                                                                        res[table[key]] = key;
                                                                                        }
                                                                                        return res;
                                                                                        },

                                                                                        //таблица скоростей и соответствующих им расстояний до точки начала координат
                                                                                        lookup_table: [
                                                                                        [0, 0],
                                                                                        [0.01, 0.2],
                                                                                        [0.02, 0.5],
                                                                                        [0.05, 1.5],
                                                                                        [0.1, 2],
                                                                                        [0.15, 2.5],
                                                                                        [0.2, 3],
                                                                                        [0.3, 3.5],
                                                                                        [0.35, 5],
                                                                                        [0.4, 5.5],
                                                                                        [0.5, 6],
                                                                                        [0.55, 10],
                                                                                        [0.6, 20],
                                                                                        [0.65, 30],
                                                                                        [2.2, 80],
                                                                                        [3, 120],
                                                                                        [4, 150],
                                                                                        ],

                                                                                        adjust_pos: async function (axis, speed = null) {
                                                                                        //соответствие осей и кнопок управления по осям
                                                                                        let ss = {'x': ['q', 'e'], 'y': ['d', 'a'], 'z': ['w', 's']}
                                                                                        if (this.abort) {
                                                                                        return null;
                                                                                        }

                                                                                        //получаем данные о положении
                                                                                        let pos = this.get_xyz();
                                                                                        //таблица подстановки вектора скорости движения и положения на оси относительно 0
                                                                                        let lookup_table = this.lookup_table;

                                                                                        //рабочий цикл будет включаться только при наличии данных о векторе скорости
                                                                                        if (speed !== null) {
                                                                                        //console.log('axis:', axis, 'speed:', speed, 'pos:', pos[axis]);

                                                                                        //получаем индекс положения корабля на оси
                                                                                        //расстояния в таблице подстановок только положительные, поэтому берем
                                                                                        //расстояние по модулю. расстояния в колонке 1 таблицы
                                                                                        let pos_idx = this.get_idx(lookup_table, pos[axis + '_abs'], 1);

                                                                                        //теперь получаем индекс из таблицы векторов скорости
                                                                                        //если положение корабля отрицательное к нулю,
                                                                                        //перевернем вектор скорости, чтобы отрицательные значения движения к нулю
                                                                                        //были положительными
                                                                                        if (pos[axis] < 0) {
                                                                                        speed = -speed;
                                                                                        }

                                                                                        //берем значение скорости и смотрим по таблице
                                                                                        //скорости в нулевой колонке
                                                                                        let speed_idx = this.get_idx(lookup_table, Math.abs(speed), 0);
                                                                                        //если хотя бы одного индекса нет, выходим
                                                                                        if (pos_idx === null || speed_idx === null) {
                                                                                        console.log('error! pos_idx or speed_idx not found.');
                                                                                        return false;
                                                                                        }
                                                                                        //console.log('speed:', speed, 'speed_idx:', speed_idx, 'pos_idx:', pos_idx);
                                                                                        //теперь мы можем определить направление и число импульсов
                                                                                        //для каждого расстояния есть желаемая скорость.
                                                                                        //для расстояния 0 скорость 0, для расстояния 0.5, скорость 0.02
                                                                                        //кол-во импульсов вычисляется исходя из того, что импульс = 0.02м/с
                                                                                        //формула: impulse = Math.floor(speed - lookup_table[pos_idx][0]) / 0.02)
                                                                                        //т.е. вычитая из текущей скорости желаемую скорость, мы определяем
                                                                                        //скорость которую надо прибавить или отнять, чтобы получить нужную
                                                                                        //делим на 0.03 (скорость 1 импульса) и получаем кол-во импульсов

                                                                                        //если отклонение от нужной скорости до 5%, ничего не делаем
                                                                                        let diff = speed - lookup_table[pos_idx][0];
                                                                                        let diff_percent = Math.abs(diff / lookup_table[pos_idx][0]);
                                                                                        if( diff_percent > 0.05) {

                                                                                        let impulse = Math.floor(diff / 0.03);

                                                                                        //направление импульса зависит от 2 вещей: направления вектора скорости,
                                                                                        // положение корабля относительно 0 осевой точки
                                                                                        //это индексы направлений импульса
                                                                                        let directions = {'1': 0, '-1': 1, '0': 0};

                                                                                        //определяем направление импульса
                                                                                        let direction = Math.sign(impulse) * Math.sign(pos[axis]);
                                                                                        //определяем кнопку
                                                                                        direction = directions[direction.toString()];
                                                                                        let key = ss[axis][direction]

                                                                                        //передаем импульс, если отклонение от нужной скорости более 5%
                                                                                        console.log('axis:', axis, 'speed:', speed, 'key:', key, impulse);
                                                                                        if(Math.abs(impulse) >= 200){
                                                                                        console.log('impulse >= 200, skip.', impulse);
                                                                                        }else {
                                                                                        await this.press(key, Math.abs(impulse));
                                                                                        }
                                                                                        }
                                                                                        }

                                                                                        //запускаем себя заново с данными о скорости
                                                                                        return this.calc_pos(axis).then(speed => this.adjust_pos(axis, speed))
                                                                                        },
                                                                                        //этим методом и производится автоматическая стыковка корабля
                                                                                        autodock: async function (abort = false) {
                                                                                        this.abort = abort;
                                                                                        this.adjust_pos('x');
                                                                                        this.adjust_pos('y');
                                                                                        this.adjust_pos('z');
                                                                                        this.adjust_axis();
                                                                                        },

                                                                                        adjust_axis: async function () {
                                                                                        let yaw = m.get_state('yaw');
                                                                                        let roll = m.get_state('roll');
                                                                                        let pitch = m.get_state('pitch');
                                                                                        let run = true;
                                                                                        while (run) {
                                                                                        if (this.abort) {
                                                                                        return null;
                                                                                        }
                                                                                        //console.log(yaw['offset'], pitch['offset'], roll['offset']);
                                                                                        if (yaw['offset'] > 0) {
                                                                                        m.set_axis('yaw', Math.sqrt(yaw['offset_abs'] / 10));
                                                                                        } else {
                                                                                        m.set_axis('yaw', -Math.sqrt(yaw['offset_abs'] / 10));
                                                                                        }
                                                                                        if (pitch['offset'] > 0) {
                                                                                        m.set_axis('pitch', Math.sqrt(pitch['offset_abs'] / 10));
                                                                                        } else {
                                                                                        m.set_axis('pitch', -Math.sqrt(pitch['offset_abs'] / 10));
                                                                                        }
                                                                                        if (roll['offset'] > 0) {
                                                                                        m.set_axis('roll', Math.sqrt(roll['offset_abs'] / 10));
                                                                                        } else {
                                                                                        m.set_axis('roll', -Math.sqrt(roll['offset_abs'] / 10));
                                                                                        }

                                                                                        await m.sleep(50);
                                                                                        yaw = m.get_state('yaw');
                                                                                        roll = m.get_state('roll');
                                                                                        pitch = m.get_state('pitch');
                                                                                        }

                                                                                        },

                                                                                        set_axis: async function (s, speed) {
                                                                                        speed = Math.round((speed + Number.EPSILON) * 10) / 10;
                                                                                        let ss = {'yaw': ['left', 'right'], 'roll': ['<', '>'], 'pitch': ['up', 'down']}
                                                                                        if (!(s in ss)) {
                                                                                        console.log(s + ' not in ', ss);
                                                                                        return;
                                                                                        }

                                                                                        let o = m.get_state(s);
                                                                                        speed *= 10
                                                                                        let cur_speed = o['speed'] * 10;
                                                                                        let distance = 0;

                                                                                        distance = speed - cur_speed;

                                                                                        if (distance > 0) {
                                                                                        while (distance--) {
                                                                                        m.press(ss[s][1]);
                                                                                        }
                                                                                        } else {
                                                                                        distance = Math.abs(distance);
                                                                                        while (distance--) {
                                                                                        m.press(ss[s][0]);
                                                                                        }
                                                                                        }
                                                                                        },

                                                                                        abort: false,

                                                                                        }

                                                                                        m.autodock();
                                                                                        //m.autodock(1); чтобы остановить автопилот
                                                                                          0
                                                                                          Попробовал код.
                                                                                          Ну… У меня fail (дважды)


                                                                                          Причем колбасить корабль начинает очень сильно… Давай опишу ситуацию. Примерно у последних ворот его начинает разгонять по амплитуде смещения. У меня такая проблема была еще на первой итерации кода, самой простой. Вот его разгонят и разгоняет… А возле самого корабля он просто нарезает круги по внешнему радиусу шлюза. Потом просто сталкивается со станцией.
                                                                                          Это я уже проходил)

                                                                                          Саму реализацию пока не смотрел, возможно вечерком гляну, в свободное время.
                                                                                            0
                                                                                            Проблема со спидометром. Время беру точное, а вот реальная скорость движения с учетом рендеринга меняется в результате скорость выдает иногда с большим отклонением. Надо подумать как еще повысить точность.
                                                                                              0
                                                                                              Так весь прикол задачи в этом и есть:
                                                                                              * Мы не имеем точных данных о состоянии объекта
                                                                                              * Мы не имеем нормальных метрик относительно системы
                                                                                              * Мы не имеем (по факту) возможность подать не «единичное» усилие. [можно обойти это ограничение, ну почти… Но в этом тоже есть свой «шарм»]

                                                                                              В реальных условиях мы бы еще имели бы кучу проблем, о которых писалось выше, типа смешения центра масс или еще чего-то. Но в симуляторе этого нет.

                                                                                              Было бы просто, я бы статью не стал бы пилить.)
                                                                                                0
                                                                                                Так весь прикол задачи в этом и есть:
                                                                                                * Мы не имеем точных данных о состоянии объекта
                                                                                                * Мы не имеем нормальных метрик относительно системы
                                                                                                * Мы не имеем (по факту) возможность подать не «единичное» усилие. [можно обойти это ограничение, ну почти… Но в этом тоже есть свой «шарм»]

                                                                                                Весь прикол задачи в том, чтобы создать эти метрики и только потом начать решать задачу управления.

                                                                                                Для этого во первых надо сделать нормальную дискретизацию — то есть если у нас алгоритм будет выполняться с периодом в 1с, то все входные параметры и измерения должны быть дискретизированы в этот домейн. Причем пусть будет задержка, фильтрация — неважно, это все можно скомпенсировать. Главное, чтобы это было учтено и мы в итоге получали скорость каждую секунду. И так далее. Потом надо обеспечить, чтобы алгоритм действительно выполнялся каждую секунду, а не как попало.
                                                                                                Я не силен в JS, поэтому и не участвую в вашей гонке, но на 90% решение задачи лежит за этим вещами.
                                                                                                  0
                                                                                                  Так нет у нас их… Надо принять как данность, что есть значения с десятичной точностью… Которых явно не хватает на последнем участке. И по-хорошему нужно учесть, что изначальное состояние может быть любое… Просто эмулятор всегда стартует с определённых значений.
                                                                                                  JS «С» подобный язык. А в решаемой задаче не требуется специфических знаний именно JS языка.
                                                                                                  Как получить значения параметров можете взять из любого приведённого здесь кода.
                                                                                                    0
                                                                                                    На последнем участке, скорей всего, надо работать не с данными, а по своей модели — т.е по данным изредка корректироваться, а расчетный импульс коррекции рассчитывать внутри кода и выдавать с четкими временными уставками.
                                                                                                    Под импульсом коррекции я понимаю последовательность из двух противоположных клавиш через определенное время, которые либо поворачивают корабль на определенный угол, либо двигают его на определенное расстояние с возвращением к нулевым скоростям после этого.
                                                                                                      0
                                                                                                      Ты почитал код моей реализации?
                                                                                                      Только я делаю это на всем протяжении пути, а не только на последнем участке.
                                                                                  0
                                                                                  Добавил автопилоту «человечности», передача управляющих команд происходит с интервалом 1мс, без интервала автопилот может парковаться на сумашедших скоростях.
                                                                                    0
                                                                                    >Но мало кто знает, что его единственный полет был беспилотным.
                                                                                    А вот тут я с Вами не согласен. Ибо ЛЮБАЯ российская публикация о полете Бурана содержит ОСОБОЕ упоминание об этом факте. Т.к. это единственное, в чем СССР обошел супостата.
                                                                                      0
                                                                                      По алгоритмам/реализациям автоматических тыковок надо у Orbiter-ов посмотреть. Возможно что уже существуют удачные реализации стыковки ;)

                                                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                      Самое читаемое