Программа узла — машина состояний: шаги и переходы между ними по условию. Внутри шага оборудование работает ОДНОВРЕМЕННО, каждое на своей дорожке. Ниже — как это устроено, какие бывают условия и пороги, и три программы целиком, взятые из каталога узлов дословно.
machine({
initial: 'загрузка',
states: {
'загрузка': {
lanes: { X: [move(120, { v: 0.5 })], зажим: [on()], датчик: [waitFor('↑')] },
entry: [on(5)], exit: [off(5)], // мгновенно на входе и на выходе
on: { 'датчик ↑': 'резка', 'X >= 120': 'резка' },
after: { 8: 'авария' }, // предел времени в этом шаге
},
'резка': { wait: 2 },
},
}) | Вид шага | Что это | Особенность |
|---|---|---|
| обычный | дорожки оборудования, действия на входе и выходе, переходы | длится по самой долгой дорожке |
| составной | шаг со вложенными шагами: сам не исполняется, исполняются дети | его переходы наследуются всеми вложенными — общая авария объявляется один раз |
| параллельный | запускает несколько соседних шагов одновременно как независимые ветки | две ветки, держащие одну ось или один порт, — это конфликт ресурса, и он называется |
| выдержка | фиксированная пауза без оборудования вовсе | длительность объявлена полем, а не выведена из дорожек |
| Дорожка | Что на неё кладётся | Замечание |
|---|---|---|
| дорожка оси | ход в позицию · вращение · прижим моментом · торможение · удержание | движения кладутся только сюда |
| дорожка выхода | включить · выключить · импульс · мигание фоном · удержание | порт известен из имени дорожки |
| дорожка входа | ждать фронт или уровень · удержание | ожидание эластично: дорожка стоит, пока сигнала нет |
Все дорожки шага стартуют одновременно, а сам шаг длится по самой долгой: остальные заканчивают раньше и ждут. Это и есть способ выразить «качаем и зажимаем разом» без второй программы.
Оборудование блока доступно по именам сразу. Код пишется словами — «лента», «зажим», «в позиции», — а с каким приводом и пином они связаны, знает паспорт стенда. Та же программа переносится на соседний участок сменой паспорта, а не переписыванием логики.
Счётчик — именованный объект программы. Один шаг прибавляет, другой сбрасывает; прибавка происходит на входе в шаг, ровно раз за проход. Памяти между пусками у счётчика пока нет: пуск начинает счёт с нуля.
Условия проверяются непрерывно, каждый такт — как скан ПЛК, а не как обработчик одиночного события. Срабатывает первый подошедший переход в порядке объявления.
| Вид | Как пишется | Что значит |
|---|---|---|
| шаг доиграл | done | все дорожки шага закончились, включая эластичное ожидание входа |
| время в шаге | '5s' · after: { 5: … } | секунды с момента входа В ЭТОТ шаг, а не с начала программы |
| вход | 'датчик ↑' · 'датчик ↓' · 'датчик =1' · 'датчик =0' | фронт или уровень; по имени из паспорта блока либо по номеру порта |
| позиция оси | 'X >= 90 град' · 'X >= 2 об' | ось доехала; единица в строке решает, какая это величина |
| скорость оси | 'лента >= 1500 об/мин' | ось разогналась |
| момент оси | 'пресс момент >= 380' | ось нагрузилась. Без единицы число читается в ПРОМИЛЛЕ номинала: 380 — это 38 % |
| счётчик | 'счётчик детали >= 6' | именованный счётчик программы: один шаг считает, другой сбрасывает |
| составное | 'деталь =1 и зажим =1' · 'или' · 'не' | до пяти простых условий на переход; не влезло — это уже состояние, назовите его шагом |
| формула | '|задатчик момент - ведомый момент| >= 120' | арифметика над величинами: до трёх действий, делить можно только на число |
| уставка рецепта | 'пресс момент >= @момент' | порог берётся из рецепта смены — программа одна, порог меняет выбор рецепта на пульте |
| авария | переход по аварии | превышение порога момента или рассогласования на любой оси шага |
| предел ожидания | { to: 'зажим', timeout_s: 5, on_timeout: 'error' } | страховка условия, которое ждёт мира: не наступило за N секунд — исход называет автор (skip — продолжить · stop — остановить · error — отказ). Предела нет — шаг ждёт без конца, и лаборатория предупреждает об этом до пуска |
| Величина | Единица | Замечание |
|---|---|---|
| позиция в ходе оси | обороты | градусы переводятся конвертером: deg(90) |
| скорость вращения | об/с | об/мин переводятся конвертером: rpm(1500) |
| прижим моментом | % номинала | знак задаёт направление |
| момент в условии перехода | ‰ номинала | 40 % прижима соответствует условию «момент >= 400» |
| время | секунды | везде, включая пределы ожидания |
Программу исполняет один из двух слоёв, и это видно по её содержимому. Чистая логика — выходы, ожидание входов, паузы, счётчики, формулы и рецепты — уезжает в логический слой и живёт ТАКТОМ стенда. Программа, которая двигает оси, исполняется плеером движения: генерация траектории — работа интерполятора, и логический слой такую программу принимать отказывается, называя конкретные блоки с движением.
Граница проведена явно в обе стороны. Условие по СЧЁТЧИКУ умеет только логический слой: плееру движения такая программа не отдаётся молча — он отказывает и называет слой, в который её надо доставить. Составное условие, формула и порог-уставка — из того же логического набора.
| Предел | По умолчанию | Замечание |
|---|---|---|
| порог момента | 600 ‰ номинала | превышение считается аварией, только если ДЕРЖИТСЯ дольше выдержки — на разгоне момент штатно уходит за предел |
| порог рассогласования | 0,05 оборота | на виртуальных осях не проверяется вовсе: рассогласование там — штатная работа модели, а не факт привода |
| выдержка аварии | 0,2 с | сколько превышение должно продержаться |
Пределы едут вместе с программой одним объектом: перегруз — свойство механизма, а не отдельного шага. На превышение можно объявить переход; переход не объявлен — останов с названной причиной.
Момент на виртуальных осях считает МОДЕЛЬ, а не привод. Он выводится из рассогласования «велено» и «есть»: свободный ход — момент холостого хода, нагрузка — рост пропорционально ошибке, упор — насыщение на уставке программы. Это годится, чтобы отладить логику поиска упора и затяжки, но замером не является и цитировать его как факт нельзя.
Нет паспортного номинала — в кадре ПРОЧЕРК, и это правильно. Шкала промилле отсчитывается от номинала двигателя; номинал не задан — точки отсчёта нет, и платформа показывает прочерк вместо правдоподобного числа. «Перегруз» при этом не объявляется: сравнивать не с чем. Условие перехода по моменту на такой оси не сработает — гоняйте такую программу на железе, там момент приходит от привода.
Образец «Позиционер CRP WB2S-250»: стол наклоняется, планшайба делает оборот под швом, стол возвращается. Показывает приём «две дорожки в одном шаге»: в шагах «исходная» и «обратный наклон» обе оси едут одновременно, шаг длится по той, что дольше.
// @rig
machine({
title: 'WB2S-250 — наклон и оборот планшайбы',
initial: 'исходная',
states: {
'исходная': {
lanes: {
'наклон': [move(deg(0), { v: 0.1 })],
'вращение': [move(deg(0), { v: 0.1 })],
},
on: { done: 'наклон' },
},
'наклон': {
lanes: { 'наклон': [move(deg(45), { v: 0.1 })] },
on: { done: 'оборот' },
},
'оборот': {
lanes: { 'вращение': [move(deg(360), { v: 0.15 })] },
on: { done: 'обратный наклон' },
},
'обратный наклон': {
lanes: {
'наклон': [move(deg(0), { v: 0.1 })],
'вращение': [move(deg(0), { v: 0.15 })],
},
on: { done: 'пауза' },
},
'пауза': {
wait: 0.5,
on: { done: 'исходная' },
},
},
}) Углы идут через deg(): сама ось считает в оборотах. Пределы хода — из механизма
(наклон ±85°, вращение ±360°); диапазоны вендор не публикует, и в файле это названо предположением.
Пролог rig() в файле не написан — на место метки // @rig его печатает
платформа из паспорта и механизма. Имена «наклон» и «вращение» — подписи каналов паспорта.
Образец «Рука KUKA KR16»: обход всех шести осей группами — карусель, плечо с локтем,
кисть. Показывает приём «очередь в дорожке»: три move подряд играются один за другим,
следующий начинается там, где кончился предыдущий.
// @rig
machine({
title: 'KR16 — обход осей',
initial: 'исходная',
states: {
'исходная': {
lanes: {
A1: [move(deg(0), { v: 0.2 })], A2: [move(deg(0), { v: 0.2 })],
A3: [move(deg(0), { v: 0.2 })], A4: [move(deg(0), { v: 0.2 })],
A5: [move(deg(0), { v: 0.2 })], A6: [move(deg(0), { v: 0.2 })],
},
on: { done: 'карусель' },
},
'карусель': {
lanes: { A1: [move(deg(60), { v: 0.2 }), move(deg(-60), { v: 0.2 }), move(deg(0), { v: 0.2 })] },
on: { done: 'плечо и локоть' },
},
'плечо и локоть': {
lanes: {
A2: [move(deg(-45), { v: 0.15 }), move(deg(0), { v: 0.15 })],
A3: [move(deg(50), { v: 0.15 }), move(deg(0), { v: 0.15 })],
},
on: { done: 'кисть' },
},
'кисть': {
lanes: {
A4: [move(deg(90), { v: 0.3 }), move(deg(-90), { v: 0.3 }), move(deg(0), { v: 0.3 })],
A5: [move(deg(45), { v: 0.2 }), move(deg(-45), { v: 0.2 }), move(deg(0), { v: 0.2 })],
A6: [move(deg(120), { v: 0.3 }), move(deg(-120), { v: 0.3 }), move(deg(0), { v: 0.3 })],
},
on: { done: 'пауза' },
},
'пауза': {
wait: 0.5,
on: { done: 'исходная' },
},
},
}) Оси зовутся так же, как шарниры в файле механизма (A1…A6) — через карту имён, потому что донор назвал их по-своему. Та же программа у образца «Рука CRP RA18-25» отличается только именами J1…J6.
Образец «Трек CRP WD250-6000» — седьмая ось робота: каретка едет по местам и
стоит на каждом. Показывает шаг-выдержку (wait) без оборудования вовсе.
// @rig
machine({
title: 'WD250-6000 — переезды каретки',
initial: 'у упора',
states: {
'у упора': {
lanes: { 'каретка': [move(0, { v: 10 })] },
on: { done: 'место 1' },
},
'место 1': {
lanes: { 'каретка': [move(100, { v: 20 })] }, // прямая ось: обороты винта, не мм
on: { done: 'работа 1' },
},
'работа 1': {
wait: 0.5,
on: { done: 'место 2' },
},
'место 2': {
lanes: { 'каретка': [move(200, { v: 20 })] },
on: { done: 'работа 2' },
},
'работа 2': {
wait: 0.5,
on: { done: 'у упора' },
},
},
}) Выдержка вместо подтверждения — это заглушка, а не решение. «Работа 1» здесь — пол-секунды паузы; в своём узле на её месте стоит ожидание входа: «дождись детали», «захват сомкнут». Пауза на глазок выдаёт «команда подана» за «сделано», и роль входа grip_ok заведена ровно затем, чтобы условие перехода звучало как «захват сомкнут», а не «прошло 0,5 секунды».
rig({ axes: { зажим: 0 }, outputs: { готово: 0 } })
machine({
title: 'Зажать до упора',
initial: 'подвод',
states: {
'подвод': {
lanes: { зажим: [move(3, { v: 0.5 })] },
// 300 ‰ = 30 % номинала. Момент читается с самого привода —
// отдельный датчик усилия для этого не нужен.
on: { 'зажим момент >= 300': 'упор', done: 'упор' },
},
'упор': {
lanes: { зажим: [push(40, { sec: 1 })] },
entry: [on(0)],
on: { done: 'отвод' },
},
'отвод': {
entry: [off(0)],
lanes: { зажим: [move(0, { v: 0.8 })] },
on: { done: 'подвод' },
},
},
}) Единицы здесь разные намеренно: прижим говорит в процентах номинала, условие перехода — в промилле. 40 % прижима соответствует условию «момент >= 400».
Состав узлов и словарь ролей каналов — на странице блоков автоматики. Путь навесного органа робота — в глаголах движения.