Межшлюзовая ГЭС
- 1 year ago
- 0
- 0
RCML ( англ. R obot C ontrol M eta L anguage , произносится [ ар-си-эм-эль ] — компилируемый статически типизированный язык программирования высокого уровня . Разработан для получения одинакового результата независимо от исполнения робота . Позволяет создавать условия для совместной работы нескольких роботов. Используется для описания действий робота или группы роботов . Включает ряд визуальных аспектов представления кода относительно классов и объектов из языков программирования, реализующих парадигму объектно-ориентированного программирования .
Синтаксис RCML близок к языкам программирования C , Java и JavaScript . Сходство синтаксиса необходимо для обеспечения лёгкого перехода для программистов с других языков .
RCML ориентирован на робототехнику и имеет довольно скудную составляющую как язык программирования, так как не предназначается для создания прикладного ПО общего назначения и нацелен на взаимодействие с робототехникой, позволяя достигать в этом отношении новых результатов.
Робот в RCML представляется как некий исполнительный ресурс, который может быть задействован для выполнения определённой задачи (функции), а затем освобождён для повторного задействования, но, например, в уже другой задаче.
Модуль робота предоставляет среде RCML описание класса робота , закреплённого за ним, предполагается, что локально в объектном мире, где используется RCML, может быть, как один, так и несколько роботов одного класса, закреплённых за одним модулем робота. Причём в среде RCML в рамках модуля робота присутствует два ключевых типа объектов, в соответствии с рисунком:
Следует отметить, что может быть подключено много классов роботов одновременно, то есть много модулей роботов, и каждый из них может предоставлять доступ сразу к нескольким роботам своего класса. Роботы в рамках одного класса должны быть одинаковы полностью, как в физическом представлении, так и в функциональном.
Модули роботов занимают одно из ключевых положений в RCML, поскольку именно через них осуществляется связь и передача команд физическому роботу.
Модуль робота отвечает за передачу команд от интерпретатора RCML одному или нескольким роботам одного класса (или типа), которые объединены под этим модулем. Рекомендуется для каждого класса или типа робота использовать отдельный модуль. Интерпретатор RCML через задекларированный API устанавливает связь с модулем робота, который в свою очередь устанавливает связь с каждым закреплённым за ним роботом. Таким образом, через модуль робота скрывается реализация связи и управления роботом от интерпретатора, что позволяет подключать к нему самых разных роботов.
Через модули функций возможно добавление в RCML новых функций, которые не целесообразно или невозможно реализовывать на данном языке, например, какие-либо сложные вычисления. Таким образом, через отдельный API модули функции позволяют реализовать связь RCML с программным обеспечением других производителей.
Модули управления служат для связи различных управляющих устройств со средой RCML с целью использования данных устройств в ручном управлении роботами, чьи модули предоставляют такую возможность. Данный тип модулей подобен модулям роботов в том плане, что через заданный API происходит разрыв зависимостей между управляющим устройством и роботом. Таким образом, реализуется возможность управления одним и тем же роботом разными устройствами, а также возможность применения одного и того же устройства управления для разных роботов. Разумеется, при этом достигается тот же эффект сокрытия реализации связи устройства управления от среды RCML и достигается возможность подключения самых разных устройств управления.
Чтобы задействовать робота в программе, необходимо указать его класс и функцию, которую он должен выполнить. Именование класса робота совпадает с именованием модуля робота в файле config.ini, но класс робота в RCML программе должен указываться через ключевое слово robot и знак подчёркивания.
Например, нужно вызвать робота из модуля test, тогда указание его класса будет иметь вид:
robot_test
Встретив наименование класса робота в тексте программы, RCML пошлёт запрос к соответствующему модулю робота и остановит выполнение программы, пока не будет найден свободный робот требуемого класса.
Функции роботов программируются разработчиком робота вместе с модулем робота и описываются в документации к модулю робота.
Вызов функции визуально похож на вызов метода объекта в
С-подобных языках программирования
. Следует указание класса робота, затем через знак указателя
->
обозначается требуемая функция, затем в круглых скобках
(
)
перечисляется список аргументов этой функции.
Синтаксис вызова функции робота:
robot_класс_робота->функция_робота(аргументы);
Например, из робота класса test нужно вызвать функцию do_something с одним аргументом 1000:
robot_test->do_something(1000);
Встретив подобную конструкцию, интерпретатор зарезервирует робота указанного класса, дождется, когда будет задействован реальный физический робот, и затем передаст команду представлению робота об исполнении указанной функции с указанными параметрами. После исполнения функции робот будет автоматически освобожден и переведен в статус свободного.
Следует отметить, что при таком указании вызова функции робота интерпретатор дождётся подтверждения выполнения функции от представления робота и только затем продолжит выполнение остальной программы.
Часто бывает необходимо вызвать не одну функцию у робота, а сразу несколько, и их должен выполнить один робот как заданную последовательность действий. Если вызвать необходимую последовательность функций, то при наличии нескольких роботов одного класса вполне вероятно, что заданные функции будут выполняться разными роботами. В случае если же робот заданного класса всего один в наличии, то для каждой функции он будет каждый раз задействован и освобождён.
Наиболее эффективно и рационально задействовать робота единожды и передавать ему команды так, как это необходимо, а затем его освободить, таким образом реализовав сеанс работы робота. Для этого требуется задействовать робота нужного класса и запомнить связь с задействованным конкретным роботом. Это можно сделать, сохраняя робота в специальный тип
переменных
, перед идентификатором которых должен быть поставлен символ
@
. Например, задействование робота класса
test
и сохранение связи с конкретным полученным экземпляром в переменной
@r
:
@r = robot_test;
Чтобы вызвать выполнение функции у данного задействованного робота, нужно вызывать функцию, обращаясь к данной переменной, а не к классу робота. Например, вызов у используемого робота той же самой функции
do_something
с параметрами:
@r->do_something(1000);
После выполнения функции робот так же останется задействованным, и можно вызывать следующую функцию у данного экземпляра.
Робота, сохранённого в тип переменной
@
, можно освободить, используя специальный оператор
delete
, когда это потребуется. С данным оператором должна указываться специальная переменная, хранящая указатель на освобождаемого робота. Пример освобождения робота, указатель на которого был ранее присвоен в переменную
@r
:
delete @r;
Все роботы, задействованные и не освобождённые через оператор
delete
, будут освобождены только при завершении выполнения функции, в которой они были задействованы. Данное утверждение не относится к функциям робота, написанным на RCML, т.к. в эти функции выполняются в контексте экземпляра робота, и в них нельзя задействовать экземпляр робота.
Одна из возможностей RCML — это автоматический подбор робота под задачу. Чтобы использовать данную возможность, нужно указывать только ключевое слово robot вместо конкретного класса робота в тех местах, где требуется указание класса робота: вызовы функций робота или присвоение робота в переменную. Например:
robot->do_something(1000);
@r = robot;
@r->do_something();
Использование только ключевого слова
robot
вместо полного имени класса робота далее будет называться
абстрактным
роботом.
В случае использования специальной переменной
@
для связи с абстрактным роботом RCML проанализирует каждый вызов функции относительно данной переменной и составит список кандидатов только из тех типов роботов, которые имеют все вызываемые функции относительно данной переменной.
Исполнение функций, написанных на RCML, может выполняться в двух основных режимах:
В случае выполнения функции «без ожидания», создаваемый поток может быть перенесён в отдельное вычислительное ядро средствами ОС , и таким образом может быть получен эффект параллельного выполнения кода на RCML.
По умолчанию все функции вызываются в режиме с ожиданием выполнения функции. Этот режим является режимом по умолчанию.
#
- флаг выполнения функции с ожиданием;
~
- флаг выполнения функции без ожидания.
~do_something(1000);
# do_anything(1000);
set
с указанием в качестве первого параметра строковой константы
"behavior"
, а в качестве второго параметра - флага режима
#
или
~
. Вызов данной функции с такими параметрами переопределяет режим выполнения функций по умолчанию, то есть, если флаг режима не будет указан явно в вызове функции, то функция будет выполняться в режиме, заданном вторым параметром функции
set
. Пример использования функции
set
:
set("behavior",~);
//все последующие вызовы функций будут выполняться
//без ожидания завершения
do_something(1000);
do_something(1000);
do_something(1000);
//режим не меняется, так как флаг совпадает со значением по умолчанию
~do_something(1000);
//явное изменение режима, но только для этого конкретного вызова функции
# do_something(1000);
В программе используется два робота. В данном случае роботы представлены тестовым модулем, который симулирует работу абстрактного робота.
По умолчанию режим выполнения функций с ожиданием.
function main() {
@rt_1=robot_test;
@rt_2=robot_test;
@rt_1->do_something(1000);
@rt_2->print("Hello world!\n", 0);
}
В результате выполнения программы тестовый робот
@rt_1
выполнит функцию
do_something
и только после этого будет задействован второй робот
@rt_2
, который выведет на экран строку
Hello world!
.
Однако, если использовать флаг без ожидания
~
и передать его функции выполняемой первым роботом
@rt_1
.
function main() {
@rt_1=robot_test;
@rt_2=robot_test;
~@rt_1->do_something(1000);
@rt_2->print("Hello world!\n", 0);
}
Выполнение программы будет происходить следующим образом. После того как передана команда
@rt_1
, происходит дальнейшее выполнение программы. Тестовый робот, тем временем,
@rt_2
выведет на экран
Hello world!
и уже после этого
@rt_1
завершит выполнение функции.
RCML позволяет обрабатывать исключительные ситуации, аналогично языкам программирования C , Java и JavaScript .
Однако в RCML, оператор
try
может принимать параметры, указывающие, как именно ему работать. Первый параметр - это строковая константа с указанием режима работы, в зависимости от указанного режима может указываться второй параметр вещественного типа данных.
Всего у оператора
try
три режима работы:
"error_default"
- режим работы по умолчанию, как обычный оператор
try
. В этом случае второй параметр оператора
try
не указывается. Если параметры оператора
try
опускаются как в вышеприведённом примере, то оператор
try
работает именно в этом режиме.
"error_time_limit"
– режим работы с отсчётом лимита времени, за который должен быть выполнен блок кода оператора
try
. В данном случае указывается второй параметр, который задаёт количество миллисекунд, являющийся лимитом на выполнение блока кода оператора
try
. В случае если данный блок не будет выполнен за указанное время, будет брошено исключение. В случае если исключение будет брошено раньше, отсчёт времени будет прекращён, а само исключение будет обработано в обычном режиме.
"error_try_count"
– режим работы с отсчётом количества попыток, данных для выполнения блока оператора
try
. В данном режиме второй параметр принимает количество допустимых попыток исполнения данного блока. При каждом брошенном исключении в блоке оператора
try
счётчик количества попыток будет уменьшаться на 1, и если он достигнет нуля, то будет произведена обычная обработка исключения.
Несмотря на то, что оператор
try
может принимать параметры, он не является функцией и не возвращает значение.
Пример использования указанных режимов для обработки успешности выполнения роботом своей функции с выдачей ему трёх попыток с лимитом времени по 2 секунды на каждую:
function main() {
try("error_try_count", 3) {
try(“error_time_limit”, 2000) {
robot->do_something();
} catch { //если время вышло
throw; //то бросаем исключение, чтобы исчерпать попытку
}
} catch {
//этот блок выполнится, когда все попытки будут исчерпаны,
//а результат так и не будет получен
}
}
Через оператор
throw
с исключением можно передать значение исключения.
Среда RCML может предоставить возможность ручного управления конкретным экземпляром робота посредством конкретного управляющего устройства при вызове системной функции
hand_control
с соответствующими параметрами.
Основной принцип действия среды RCML при переходе в режим ручного управления заключается в связывании осей робота, по которым он может так или иначе передвигаться, с осями управляющего устройства, по которым данное устройство может фиксировать изменения.
На рисунке есть гусеничный робот (изображён слева), который может переходить в своё новое абсолютное положение на плоскости посредством ряда изменений своего положения по двум осям: оси передвижения
R
(вперёд или назад) и оси вращения
A
(влево или право). И есть простое управляющее устройство по типу джойстика (изображено справа), которое может отклоняться в плоскости от своего начального положения по двум осям
X
и
Y
. Соответственно, через RCML возможно связать оси джойстика и робота так, чтобы отклонение джойстика приводило к движению робота. Например, отклонение джойстика по оси
Y
в положительную сторону приводило к движению вперёд, а отклонение джойстика по оси
X
в отрицательную сторону приводило к повороту робота влево. Предположим, что данный робот представлен в среде RCML модулем робота
parrot
, а джойстик, соответственно, модулем управления
joy
, тогда RCML код для их связи в режиме ручного управления для получения эффекта, приведённого в примере, будет следующим:
@r = robot_tarakan;
hand_control(@r, “joy”, “R”, “Y”, “A”, “X”);
У представления робота в среде RCML есть очередь команд , которая наполняется командами путём вызова функций робота из кода на RCML. При поступлении команды в пустую очередь, команда будет передана роботу на исполнение. Пока исполняется первая команда, все вновь поступившие команды становятся в очередь. Робот выполняет функцию в материальном мире обычно медленнее, чем RCML интерпретатор успевает выполнить очередной код на RCML и дойти до следующего вызова функции робота, т.е. обычно действия робота "медлительнее" действий вычислительного процессора.
Однако бывает иная ситуация, когда роботу необходимо выполнить серию быстрых перемещений без остановки, а расчёт параметров этих перемещений занимает больше времени, чем выполняется перемещение. Тогда эффективнее заранее рассчитать параметры перемещений и передать роботу сразу пакет команд с рассчитанными параметрами, чтобы робот не замедлялся в ожидании очередной команды. Бывают ситуации, что сам механизм вызова функций работает медленнее, чем робот исполняет команды, и возникает задержка в быстрой серии перемещений.
Чтобы скомпенсировать этот эффект, был введён механизм пакетной передачи команд роботу. Команды, получаемые посредством вызова функций робота, можно скомпоновать в единый пакет и передать целиком представлению робота. Чтобы отправить команду в пакет, нужно поставить перед вызовом функции символ
>
. Чтобы отправить пакет на выполнение, нужно вызвать системную функцию
send_package()
.
//отправка команды в пакет
>robot_test->do_something(1000);
//отправка пакета на выполнение
system.send_package();
В данном примере сначала будет послан запрос на свободного робота класса
test
, и только когда робот будет найден, функция будет передана в пакет, и затем пакет будет отправлен. Как и в вызове функций робота, при пакетной передаче команд роботы исполнители резервируются заранее. После вызова функции
send_package
возможно формирование нового пакета, в том числе и не дожидаясь исполнения предыдущего пакета при помощи использования флага
~
выполнения функции.
Возможно составление пакета сразу для двух роботов, команды из этого пакета будут переданы сразу двум представлениям роботов, дополнительно можно комбинировать типы вызовов функций.
>robot_test->do_something(1000);
>robot_test->do_something(1000);
system.send_package();
Согласно данному примеру, будут задействованы два робота одновременно, а не один и тот же два раза подряд. В таком случае, выполнение первых команд в очереди каждого представления робота начнётся одновременно. Данный механизм позволяет синхронизировать начало выполнения разными роботами своих команд.
Простейшая программа на RCML имеет следующий вид:
function main() {
return;
}
Программа сразу после запуска завершит работу.
Вывод в консоль строки " Hello world! ", через модуль тестового робота.
function main() {
robot_test->print("Hello world!\n", 0);
}
Как отмечалось ранее RCML ориентирован на работу с пулом (множеством) роботов, из которого выделяются исполнители для решения динамически возникающих задач.
Пусть есть
пул
роботов, некоторые роботы из этого пула способны выполнить необходимую технологическую функцию
do_something()
. Необходимость выполнения данной функции определяет некий внешний сенсор, тогда программа для динамического распределения задач выполнения данной функции будет иметь вид:
function main() {
loop {
have_new_task = get_data_from_sensor();
if (have_new_task) {
~robot->do_something();
}
system.sleep(300);
}
}
В данной программе в цикле с интервалом 300 мс, опрашивается внешний сенсор посредством функции
get_data_from_sensor()
, строка 3. В случае если возникла необходимость выполнения функции, то будет задействован первый свободный робот из пула, способный выполнить функцию
do_something()
, строка 5. При этом программа не будет ожидать выполнения функции роботом, т.к. установлен флаг выполнения функции без ожидания
~
. Это позволит программе не замедлиться в период выполнения роботом своей функции и продолжить опрашивать сенсор с заданным интервалом.
В случае если через очередные 300 мс, снова потребуется выполнение функции, а первый задействованный робот ещё не закончил работу, то RCML задействует второго робота из пула, и т.д. После выполнения заданной функции роботы будут автоматически освобождены и возвращены в общий пул. В случае если все роботы будут задействованы, программа будет ожидать освобождения робота.
Данный приём позволяет динамически распределять среди роботов задачи из очереди и задействовать сразу нескольких роботов.
Пусть имеется задача перемещения
деталей
весом от 1 до 15 кг, детали поступают последовательно, однако их необходимо перемещать по возможности наиболее быстро. Имеется
пул
разнотипных роботов, среди которых роботы класса
robot_heavy
большей грузоподъёмности (до 10 кг) и
robot_light
меньшей грузоподъёмности (до 5 кг). При этом
robot_heavy
перемещает деталь за 10 секунд, а
robot_light
за 5 секунд. Таким образом, выполняемые роботами задачи параметризованы, и на основе имеющегося параметра (веса детали) необходимо принимать наиболее рациональное решение какой тип робота задействовать, чтобы обеспечить минимальный простой и максимальную производительность участка. С целью показать применение пакетов команд в рамках данного примера допустим, что деталь весом более 10 кг могут нести два робота одновременно.
function main() {
loop {
detail_weight = get_weight_from_sensor(); //Получение веса детали
if (detail_weight < 5) { //Если вес до 5 кг
~robot->move_detail(); //Можно задействовать любого робота
}
if ((detail_weight >= 5) && (detail_weight < 10)) { //Если вес от 5 до 10 кг
~robot_heavy->move_detail(); //Можно задействовать только более грузоподъемного робота
}
if (detail_weight >= 10) { //Если вес от 10 кг
>robot_heavy->move_detail(); //Один робот обязательно должен быть более грузоподъемный
>robot->move_detail(); //Второй робот может быть любым
~system.send_package(); //Отправка пакета команд для роботов на выполнение
}
system.sleep(300);
}
}
В случае если вес детали менее 5 кг, то деталь может нести робот любого класса, строка 5. Однако, RCML сначала опросит всех роботов класса
robot_light
и если среди них не окажется свободного, то будет произведён опрос роботов класса
robot_heavy
(Приоритет опроса классов роботов задаётся в конфигурации интерпретатора RCML). Первый свободный робот будет задействован для перемещения, по аналогии с предыдущим примером, без ожидания основной программы выполнения роботом своей функции - перемещения детали. Таким образом RCML попытается задействовать сначала робота наиболее подходящего класса
robot_light
, а если свободного робота такого класса нет, то будет задействован робот менее подходящего класса
robot_heavy
, чтобы не допустить простоя.
Однако в случае, если вес детали от 5 до 10 кг, то можно задействовать только более грузоподъемного робота, строка 7.
В случае если вес детали от 10 кг, то надо задействовать двух роботов, среди которых один должен быть более грузоподъёмным, а второй любым. Стоит отметить, что в данном случае команда на перемещение детали передаётся двум роботам одновременно, посредством механизма составления пакетов команд (строки 11-15).
Следует отметить, что данный пример предполагает, что детали поступают строго последовательно, т.е. следующая деталь поступает только тогда, когда робот забрал предыдущую и переносит её некоторое время. Таким образом, очередь заданий для роботов также последовательна и в случае, если поступает несколько тяжёлых деталей, а затем лёгкая, то лёгкая деталь будет перемещена, только когда все тяжёлые детали будут перемещены, из-за этого возможен простой роботов класса
robot_light
.
Усложним предыдущий пример. Пусть детали поступают в некий контейнер беспорядочно, система технического зрения наблюдает контейнер и распознает детали в нём, получая координаты и тип детали, а по типу определяет её вес. При распознавании очередной детали нужно поставить задачу роботам на перемещение детали. При распознавании деталей некая функция
get_new_detail_index()
будет возвращать уникальный индекс распознанной детали, по которому впоследствии можно получить координаты и вес детали, необходимые для принятия решения по задействованию роботов и перемещению, соответственно.
function executeMoveTask(detail_index) {
detail_weight = get_weight_by_index(detail_index); //Получение веса детали
detail_coords = get_coords_by_index(detail_index);
if (detail_weight < 5) { //Если вес до 5 кг
~robot->move_detail(detail_coords); //Можно задействовать любого робота
}
if ((detail_weight >= 5) && (detail_weight < 10)) { //Если вес от 5 до 10 кг
~robot_heavy->move_detail(detail_coords); //Можно задействовать только более грузоподъемного робота
}
if (detail_weight >= 10) { //Если вес от 10 кг
>robot_heavy->move_detail(detail_coords); //Один робот обязательно должен быть более грузоподъемный
>robot->move_detail(detail_coords); //Второй робот может быть любым
~system.send_package(); //Отправка пакета команд для роботов на выполнение
}
}
function main() {
loop {
new_detail_index = get_new_detail_index(); //Получение очередного индекса детали
if (new_detail_index) { //Новая деталь поступила
~executeMoveTask(new_detail_index); //Выполняем задачу по перемещению
}
system.sleep(300);
}
}
В данном примере при поступлении новой детали в контейнер, будет получен её уникальный индекс (строка 19), который будет передан в функцию
executeMoveTask
(строка 21), в которой выполняется задействование роботов, т.е. по сути формирование запросов к пулу роботов. Особо следует отметить, что данная функция вызывается с флагом без ожидания
~
.
В сумме это даёт следующий эффект: если в контейнер поступило некоторое большое количество тяжёлых деталей весом до 10 кг, и из пула свободных роботов были задействованы все грузоподъёмные роботы
robot_heavy
, а затем в контейнер поступило некоторое количество легкий деталей весом до 5 кг, то RCML сможет задействовать ранее простаивающих роботов класса
robot_light
до того как роботы класса
robot_heavy
переместят все тяжёлые детали. Переместив задействование робота в отдельную функцию выполняемую без ожидания по сути мы получили возможность формировать различные очереди задач для различных классов роботов. Таким образом, простой роботов при наличии подходящих для них задач, будет сведён к минимуму, что было невозможно в предыдущем примере при строгой очерёдности поступления задач.
RCML даёт возможность программисту явно указать, что некоторые роботы могут выполнять одну и ту же функцию одинаково и тем самым они могут считаться взаимозаменяемыми при выполнении данной функции, хотя при этом роботы имеют различный API предоставляемый для программиста на уровне RCML.
В вышеприведённых примерах встречаются строки вида
robot->move_detail()
, с использованием ключевого слова
robot
. Данное ключевое слово сообщает RCML, что для выполнения данной функции можно задействовать любого робота из пула обладающего запрашиваемой функцией
move_detail()
.
Пусть у классов роботов
robot_heavy
и
robot_light
имеется набор функций для управления захватом и перемещением захвата, однако функции в этих набор имеют разные имена и отличающиеся параметры.
Следующий пример показывает как унифицировать API роботов в рамках конкретной программы, чтобы разные классы роботов могли выполнять одну и ту же функцию.
function robot_heavy::move_to(x, y, z, w, p, r) {
//Далее следует код для перемещения в заданную точку,
//путём обращения к функциям специфичным для роботов класса robot_heavy
robot->set_real_di("x",x);
robot->set_real_di("y",y);
robot->set_real_di("z",z);
robot->set_real_di("w",w);
robot->set_real_di("p",p);
robot->set_real_di("r",r);
robot->go_position();
}
function robot_heavy::gripper(s) {
//Специфичный, для robot_heavy, код управления захватом
if (s) {
robot->set_gripper_pos(124,25);
} else {
robot->set_gripper_pos(350,50);
}
}
function robot_light::move_to(x, y, z, w, p, r) {
//Специфичный, для robot_light, код перемещения захвата
robot->move_to(x,y,z);
robot->set_angle(w,p,r);
}
function robot_light::gripper(s) {
//Специфичный, для robot_light, код управления захватом
if (s) {
robot->pump_on();
} else {
robot->pump_off();
}
}
function main() {
//Универсальный код для перемещения детали роботом любого класса
robot->move_to(46,76,73,235,-34,23); //Переместиться к заготовке
robot->gripper(1); //Захватить деталь
robot->move_to(235,34,47,262,673,74); //Переместить деталь к позиции 1
system.sleep(15000); //Ожидать время измерения детали на позиции 1
if (some_check()) {
//Переместить захват робота к контейнеру с качественными деталями
robot->move_to(35,63,23,25,-48,245);
robot->gripper(0); //Освободить деталь
} else {
//Переместить захват робота к контейнеру с браком
robot->move_to(568,778,346,-54,2,34);
robot->gripper(0); //Освободить деталь
}
}
В данном случае алгоритм перемещения и проверки сможет выполнять как класс роботов
robot_heavy
, так и класс роботов
robot_light
, хотя они имеют разный
API
предоставляемый на уровне RCML.