Comments 96
И естественно, пытаясь реагировать на них каждые 200 миллисекунд, у нас получалось очень сильные действия по регулированию. Мы слишком много раз «тыкали на кнопки» при малейшем отклонении. А чем ближе к кораблю, тем сильнее значения смещения начинали скакать и мы фактически еще больше раскачивали корабль…
Но ведь ПИД же есть. :)
Сам симулятор расположен на сайте сайте и представляет собой, довольно проблематичную, на первый взгряд игрушку…Ничего сложного в игрушке нет. Выводишь угловые отклонения в ноль и действуя только смещениями наводишься на центр шлюза. Даёшь импульс вперёд и короткими боковыми импульсами удерживаешь центр. Перед самой стыковкой тормозишь до минимума.
В общем, после стыковок в KSP, особенно когда смещён центр масс и цель тоже слегка поворачивается, эта игрушка проходится на ура с первого раза.
Хм… Могу только порадоваться за Вас:) Ибо я сначала пробовал только по визуальным ориентирам проходить, но безуспешно. До шлюза доходил и получал провал, не выполнив одно из условий.
ru.wikipedia.org/wiki/Elite
Американские Шатлы всегда приземлялись только в ручном режиме. Это было удивительно, принимая во внимание тот факт, что компьютеры раньше были не мощнее калькулятора.
Спейс Шатлы садились в автоматическом режиме до высоты примерно в 15 тысяч метров и пилот брал управление на себя, когда скорость становилась уже дозвуковая, когда до посадки оставалась пару минут. Полная автоматическая посадка была доступна до касания ВПП и испытывалась в одной из первых миссий еще в 82-ом году. И то, что она была возможно только до касания ВПП, — было не техническим, а концептуальным ограничением. Невозможность посадки челнока без человека внутри было требованием пилотов при разработке: им хотелось хоть немного порулить, они ведь военные пилоты, а тут их хотят какой-то машиной заменить :)
Так что Буран здесь не совершил никакого прорыва.
Материалов вот маловато на тему того, что после Колумбии удалось заавтоматизировать на Шаттле тем самым проводом, а то таки мешало(?) нормальной посадке со всеми процедурами.
Материалов вот маловато на тему того, что после Колумбии удалось заавтоматизировать на Шаттле тем самым проводом, а то таки мешало(?) нормальной посадке со всеми процедурами.Материалов вполне хватает (если владеть English), а «что мешало» — как ни странно, пресловутый «человеческий фактор».
В отличие от советской космонавтики, где космонавты не очень-то отличались от Белки и Стрелки (или чукчи из широко известного анекдота), американские программы всегда ориентировались на человека-пилота.
Поделитесь линком. Везде remote control capabilities без подробностей, т.е. не ясно, старт ВСУ, выпуск атмосферных сенсоров и т.д. выполнялись полностью автоматически или по команде с Земли. Костыль всё-таки, да и писали бы прямым текстом про автономность, но вот нет.
Я п присоветовал с вопросом-то разобраться.
Очень много ручных операций на орбите. НАчиная от ручных стыковок (бывают) до перестыковки кораблей от одного стыковочного узла на другой. Вручную…
Очень высокая подготовка космонавтов, именно как пилотов кораблей. И не у всех, кстати, получается — очень специфичная техника…
Ну и кучи аварийных ситуаций, где пристыковывались из, казалось, невозможных ситуаций менно вручную.
И под Буран был готов целый отряд, «Волчья стая», куда были набраны высококлассные лётчики-испытатели…
Так что про белок-стрелок-чукч, это вы мимо… уж извините. Это, если вкратце…
А насчёт автоматики — так этоизначально так должно быть. Мало ли что с космонавтом. Он должен быть жив-здоров, пристыкован/отстыкован и вернут на Землю… Такая практика шла изначально. И у нас, и у них…
ЗЫ: Бориса Евсеевича имел честь хоть чуть-чуть, но знать лично. Книга его перечитана не раз.
С уважением.
Да и фраза "компьютеры раньше были не мощнее калькулятора" мягко говоря смущает. Из вики:
Бортовой комплекс управления состоит примерно из пятидесяти программных систем, на базе компьютера IBM System/370. Часть системных команд из IBM-набора S/370 не была реализована, в то же время было добавлено много оригинальных команд общего назначения, не имеющих аналогов в IBM-наборе. На борту корабля находилось два комплекта БЦВМ «Бисер-4» по четыре аппаратно-параллельных компьютера и аппаратного компаратора, допускающего автоматическое отключение подряд двух компьютеров в случае аварийных результатов (4 основных + 4 резерв).
Я так думаю, что этот "набор" с десяток пилотов (работающих слаженно) заменил. Поэтому преподносимый автопилот как какое-то достижение по мне как раз наоборот.
Не помню уже источник, но очень нравится это высказывание (своими словами): "… в СССР космос осваивали ракетчики, а в Штатах — пилоты самолетов"
Под спойлером полный код. Вы можете сами проверить его работоспособность на сайте iss-sim.spacex.com
Инструкция б не помешала бы. Заодно может организовать соревнования по наилучшему (самому быстрому, самому экономному) алгоритму автоматической стыковки?
Условный расход топлива можно считать по количеству импульсов и их длительности.
Инструкция б не помешала бы.
1) Открыть сайт и запустить эмулятор.
2) Открыть консоль F12
3) Открыть вкладку Console
4) Вставить код из под «Спойлера». Нажать Enter. :)
5) Наслаждаться. :)
—
Заодно может организовать соревнования по наилучшему (самому быстрому, самому экономному) алгоритму автоматической стыковки?
Я только ЗА! :)
Но я не у верен, что найдутся еще энтузиасты.
Экономный будет довольно долгим, но около 20 импульсов хватит: 6 на выравнивание по курсу/крену/тангажу, 4 на грубое выравнивание осей, остальное — на разгон/торможение и тонкое выравнивание. Главное — не пытаться постоянно держать строго нули, позволить немного "гулять", окончательно свести оси лишь непосредственно перед касанием парой-другой импульсов.
PS: браузер — Firefox. Пришлось заменить outerText на textContent — как советовали выше.
У меня вышло успешно. У Вас не получилось?
По Поводу ПИД — в ваших руках сделать лучше) Я не против посоревноваться. Мне кажется, проблема в очень дискредитизированных значениях, которые мы считываем… Они не только ведь дискретны, но и еще идут с определённой задержкой от управляющего воздействия. Но, как я уже сказал, я готов посмотреть и возможно даже посоревноваться с Вашим решением.
Стыковка по-ковбойски (судя по всему люди, а не скрипты):
Кто уложится в 20 секунд?
Хотя это скорее инженерная работа Королёва Сергея Павловича, который к сожалению, так и не побывал в космосе, о котором мечтал…
Ну, баллистика-то скорее работа не С.П.Королева, а все-таки баллистиков. Например, достаточно известен один из руководителей отдела Энергии Р.Ф. Аппазов, про которого даже страница в Википедии есть, и который эту самую баллистику нам читал в свое время.
Простите, немного не по теме, но давно мучает вопрос: почему нельзя в космосе на определенном моменте сближения пробросить с одного корабля на другой трос и потом просто притянуть один корабль к другому. Мне кажется, автоматическую систему бросания/ловления троса реализовать совсем просто. А если стягивать по оси стыковочных узлов, то корабли автоматически центрируются.
Вы притягиваете трос и начинаете двигаться к станции. Во-первых Вы двигаете еще и станцию, меняя ее орбиту. Во вторых вы начали двигаться в направлении станции, как тормозить?
Потому что космос — не вода. Если корабль смотрит носом не точно на лебёдку и если трос прикреплен к носу корабля, то при первоначальном рывке троса корабль начнет вращаться и остановить вращение можно будет только двигателями.
Да и вообще, как вы представляете перебрасывание троса? Это надо буквально выстрелить им из станции и попасть в какой-то приемный порт корабля. Учитывая, что корабль тоже движется и вращается. И что там снаружи торчат хрупкие антенны и солнечные батареи.
Проще уж воспользоваться канадармом, но он есть только на МКС. Т.е. это — не универсальное решение.
Ну и кроме соосности в стыковочном узле необходимо ещё совпадение угла поворота, иначе не сработают сцепные механизмы шлюза.
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)
Но вообще, идет к цели хорошо…
Запостил сюда, кстати github.com/vkalinsky/spacexiss-autodocker
Хотя… Получается это чуть-чуть, в обход «обозначеных условий» задачи. Вопрос уже не к решению, а к постановке. Но пусть каждый решает сам для себя насколько допустимо такое.
Плюс очень уж много манипуляций происходит. Если подсчитать расход топлива, то совсем неэффективно получается.
Походу все-таки придется мне сесть и написать свое решение…
Я учитывая, что мое решение, которое я писал в свободное после работы время пару вечеров назвали «дрянью», очень жду решения от вас.
Я могу сделать решение на основе релейной логики или ТАУ с описанием модели КК в виде интеграторов. Но это же классика!
Где ИИ? Где нейросети и машинное обучение? Вот это я ожидал увидеть, а не джаваскрипт, который даже не может пристыковаться с первого раза. Решение задачи в виде «я скормил параметры yaw, pitch и speed моему пет-проектному ИИ фреймворку, который выдал мне команды на нажатие кнопок и все заработало» — где оно?
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); чтобы остановить автопилот
Ну… У меня fail (дважды)
Причем колбасить корабль начинает очень сильно… Давай опишу ситуацию. Примерно у последних ворот его начинает разгонять по амплитуде смещения. У меня такая проблема была еще на первой итерации кода, самой простой. Вот его разгонят и разгоняет… А возле самого корабля он просто нарезает круги по внешнему радиусу шлюза. Потом просто сталкивается со станцией.
Это я уже проходил)
Саму реализацию пока не смотрел, возможно вечерком гляну, в свободное время.
* Мы не имеем точных данных о состоянии объекта
* Мы не имеем нормальных метрик относительно системы
* Мы не имеем (по факту) возможность подать не «единичное» усилие. [можно обойти это ограничение, ну почти… Но в этом тоже есть свой «шарм»]
В реальных условиях мы бы еще имели бы кучу проблем, о которых писалось выше, типа смешения центра масс или еще чего-то. Но в симуляторе этого нет.
Было бы просто, я бы статью не стал бы пилить.)
Так весь прикол задачи в этом и есть:
* Мы не имеем точных данных о состоянии объекта
* Мы не имеем нормальных метрик относительно системы
* Мы не имеем (по факту) возможность подать не «единичное» усилие. [можно обойти это ограничение, ну почти… Но в этом тоже есть свой «шарм»]
Весь прикол задачи в том, чтобы создать эти метрики и только потом начать решать задачу управления.
Для этого во первых надо сделать нормальную дискретизацию — то есть если у нас алгоритм будет выполняться с периодом в 1с, то все входные параметры и измерения должны быть дискретизированы в этот домейн. Причем пусть будет задержка, фильтрация — неважно, это все можно скомпенсировать. Главное, чтобы это было учтено и мы в итоге получали скорость каждую секунду. И так далее. Потом надо обеспечить, чтобы алгоритм действительно выполнялся каждую секунду, а не как попало.
Я не силен в JS, поэтому и не участвую в вашей гонке, но на 90% решение задачи лежит за этим вещами.
JS «С» подобный язык. А в решаемой задаче не требуется специфических знаний именно JS языка.
Как получить значения параметров можете взять из любого приведённого здесь кода.
Под импульсом коррекции я понимаю последовательность из двух противоположных клавиш через определенное время, которые либо поворачивают корабль на определенный угол, либо двигают его на определенное расстояние с возвращением к нулевым скоростям после этого.
А вот тут я с Вами не согласен. Ибо ЛЮБАЯ российская публикация о полете Бурана содержит ОСОБОЕ упоминание об этом факте. Т.к. это единственное, в чем СССР обошел супостата.
Стыкуемся с МКС с помощью JavaScript и циркуля