This repository has been archived on 2024-09-18. You can view files and clone it, but cannot push or open issues or pull requests.
sdp-scheduler/utils.c

708 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by Владислав Остапов on 13.12.2022.
//
#ifdef EMULATOR
#include <stdio.h>
#include "emulator.h"
#else
#include "utils.h"
#endif
#define LOCK_ZONE_BORDER 1
const short NIGHT_ZONES[9] = {
ZONE_LOAD_1,
ZONE_WASHING_1A, ZONE_WASHING_1B,
ZONE_WASHING_2A, ZONE_WASHING_2B,
ZONE_WASHING_3A, ZONE_WASHING_3B,
ZONE_WASHING_4A, ZONE_WASHING_4B
};
char zone_is_busy(short zone) {
for (short i = 0; i < BARRELS_COUNT; i++) {
if (barrels[i].flags.is_exist && barrels[i].zone == zone) {
return 1;
}
}
return 0;
}
short get_first_night_zone() {
// всего зон, куда можно сныкать барабаны (всего 9 мест: 8 промывок и выгрузка)
for (short i = 0; i < 9; i++) {
if (!zone_is_busy(NIGHT_ZONES[i])) {
return NIGHT_ZONES[i];
}
}
return -1;
}
// TODO обновить метод для работы с двумя роботами
// вернет можно ли ехать и главное куда ехать, если можно (нельзя если вернулось значение < 0)
// -1 вернет что перемещать нельзя
short can_move(struct barrel *bar, char robot_id) {
if (robot_id != 1 && robot_id != 2) {
return -1;
}
// сразу отсекаем варианты, при которых невозможно переместить барабан
if (!bar->flags.is_exist) {
return -1;
}
if (bar->software_timer > 0) {
return -1;
}
if (bar->flags.robot != 0) {
return -2;
}
// проверка ночного режима
if (hla_night_mode) {
if (bar->flags.is_night && !bar->flags.is_empty) {
return -1;
}
} else {
}
// дальше нужно проверить, что можно передвигать бочку
short dest_zone = -1;
// если барабан ночной, то надо проверить выгрузку (туда выгружаются ночные барабаны)
if (bar->flags.is_night) {
if (!zone_is_busy(ZONE_LOAD_1)) {
dest_zone = ZONE_LOAD_1;
}
} else {
switch (bar->zone) {
case ZONE_LOAD_2:
// загрузка 2, только в нее можно грузить новые барабаны, нужно обезжиривание
if (!zone_is_busy(ZONE_DEGREASING)) {
dest_zone = ZONE_DEGREASING;
}
break;
case ZONE_DEGREASING:
// обезжиривание, нужна промывка 1А
if (!zone_is_busy(ZONE_WASHING_1A)) {
dest_zone = ZONE_WASHING_1A;
}
break;
case ZONE_WASHING_1A:
// промывка 1А, нужна промывка 1Б
if (!zone_is_busy(ZONE_WASHING_1B)) {
dest_zone = ZONE_WASHING_1B;
}
break;
case ZONE_WASHING_1B:
// промывка 1Б, нужно травление (зоны 5-6)
if (etching_zone < 0) {
break;
}
if (!zone_is_busy((short)(ZONE_ETCHING_1 + etching_zone))) {
dest_zone = (short)(ZONE_ETCHING_1 + etching_zone);
}
break;
case ZONE_ETCHING_1:
case ZONE_ETCHING_2:
// травление, нужна промывка 2А
if (!zone_is_busy(ZONE_WASHING_2A)) {
dest_zone = ZONE_WASHING_2A;
}
break;
case ZONE_WASHING_2A:
// промывка 2А, нужна промывка 2Б
if (!zone_is_busy(ZONE_WASHING_2B)) {
dest_zone = ZONE_WASHING_2B;
}
break;
case ZONE_WASHING_2B:
// промывка 2Б, нужно цинкование (зоны 9-16)
if (galvanizing_zone < 0) {
break;
}
if (!zone_is_busy((short)(ZONE_GALVANIZING_1 + galvanizing_zone))) {
dest_zone = (short)(ZONE_GALVANIZING_1 + galvanizing_zone);
}
break;
case ZONE_GALVANIZING_1:
case ZONE_GALVANIZING_2:
case ZONE_GALVANIZING_3:
case ZONE_GALVANIZING_4:
case ZONE_GALVANIZING_5:
case ZONE_GALVANIZING_6:
case ZONE_GALVANIZING_7:
case ZONE_GALVANIZING_8:
// цинкование, требуется чтобы в зонах 17-22 было максимум 2 барабана (3 барабана для этой части линии - максимум)
if (!zone_is_busy(ZONE_WASHING_3A)) {
short count = 0;
// если зона 17 свободна, то диапазон начнется с
for (short i = ZONE_WASHING_3B; i <= (short)ZONE_UNLOAD; i++) {
if (zone_is_busy(i)) {
count++;
}
}
if (count < 3) {
dest_zone = ZONE_WASHING_3A;
}
}
break;
case ZONE_WASHING_3A:
// промывка 3А, перекладываем в промывку 3Б
if (!zone_is_busy(ZONE_WASHING_3B)) {
dest_zone = ZONE_WASHING_3B;
}
break;
case ZONE_WASHING_3B:
// это перед пассивацией, требует свободную промывку 4А и на всякий случай свободную пассивацию
if (!zone_is_busy(ZONE_PASSIVATION) && !zone_is_busy(ZONE_WASHING_4A)) {
dest_zone = ZONE_PASSIVATION;
}
// это атомарная операция, по идее вносить барабан в пассивацию нельзя
break;
case ZONE_PASSIVATION:
// процесс пассивации, нужна промывка 4A
// чисто теоретически сюда никогда не попадем, но если вдруг выстрелит пусть будет
if (!zone_is_busy(ZONE_WASHING_4A)) {
dest_zone = ZONE_WASHING_4A;
}
break;
case ZONE_WASHING_4A:
// промывка 4А, перекладываем в промывку 4Б
if (!zone_is_busy(ZONE_WASHING_4B)) {
dest_zone = ZONE_WASHING_4B;
}
break;
case ZONE_WASHING_4B:
// процесс пассивации, нужна промывка 4B (зона 21) (потому что сейчас я в 4A)
if (!zone_is_busy(ZONE_UNLOAD)) {
dest_zone = ZONE_UNLOAD;
}
break;
case ZONE_UNLOAD:
// последняя промывка, нужно разрешение на выгрузку
if (one_robot_mode && button_unload) {
// нужно промывку загрузку 0
if (!zone_is_busy(ZONE_LOAD_1)) {
dest_zone = ZONE_LOAD_1;
}
}
break;
}
}
if (!one_robot_mode) {
if (robot_id == 1) {
// если робот 1, то это старый, который ближе к концу линнии.
// Ему нельзя ехать если хоть одна из зон <= max(r2_pos, r2_lock) + кол-во пограничных зон
short border = robot2.dx.current_zone;
if (robot2_lock_zone > border) {
border = robot2_lock_zone;
}
border += LOCK_ZONE_BORDER;
if (bar->zone <= border || dest_zone <= border) {
dest_zone = -1;
}
} else {
// если робот 2, то это новый, который ближе к началу линнии.
// Ему нельзя ехать если хоть одна из зон >= max(r2_pos, r2_lock) - кол-во пограничных зон
short border = robot1.dx.current_zone;
if (robot1_lock_zone < border) {
border = robot1_lock_zone;
}
border -= LOCK_ZONE_BORDER;
if (bar->zone >= border || dest_zone >= border) {
dest_zone = -1;
}
}
}
return dest_zone;
}
// выставляет приоритет операции
short get_operation_priority(short barrel_id) {
// сделать приоритет на барабан, который больше всего ждет
if (barrels[barrel_id].zone >= ZONE_GALVANIZING_1 && barrels[barrel_id].zone <= ZONE_GALVANIZING_8) {
// теперь надо выяснить, есть ли барабаны с большим временем ожидания
// тут возможны несколько случаев:
// 1) когда барабан один такой (больше в цинковании нет барабанов чтобы их изъять),
// 2) когда барабанов несколько, соответственно если есть барабан с наибольшим временем, то надо ему дать приоритет 1, а остальным 0,
char is_not_one = 0;
char is_with_max_time = 1;
for (short i = 0; i < BARRELS_COUNT; i++) {
if (i == barrel_id) {
continue;
}
if (barrels[i].flags.is_exist && barrels[i].zone >= ZONE_GALVANIZING_1 && barrels[i].zone <= ZONE_GALVANIZING_8) {
if (can_move(barrels + i, 1) >= 0) {
is_not_one = 1;
// чем больше у барабана время ожидания тем меньше у него число
if (barrels[i].software_timer < barrels[barrel_id].software_timer) {
is_with_max_time = 0;
break;
}
}
}
}
if (is_not_one == 0) {
return 1;
}
return is_with_max_time;
}
// теперь то же самое, только для травления (только тут задача проще потому что травления всего 2 зоны, и надо только один барабан если он есть)
if (barrels[barrel_id].zone >= ZONE_ETCHING_1 && barrels[barrel_id].zone <= ZONE_ETCHING_2) {
// если в травлении барабан лежит больше 40 минут, ему присвоить высокий приоритет
if (barrels[barrel_id].software_timer < (-1 * 60 * 40)) {
return 2;
}
for (short i = 0; i < BARRELS_COUNT; i++) {
if (i == barrel_id) {
continue;
}
if (barrels[i].flags.is_exist && barrels[i].zone >= ZONE_ETCHING_1 && barrels[i].zone <= ZONE_ETCHING_2) {
if (can_move(barrels + i, 1) >= 0) {
if (barrels[i].software_timer < barrels[barrel_id].software_timer) {
return 0; // у этого барабана больше время ожидания (число меньше), значит приоритет 0
} else {
return 1; // поскольку у нашего барабана максимальное время ожидания
}
}
}
}
// барабан не найден, приоритет ему 1
return 1;
}
return 1; // 1 - нормальный приоритет
}
char remove_barrel_from_zone(short zone) {
for (short i = 0; i < 10; i++) {
if (barrels[i].flags.is_exist && barrels[i].flags.robot == 0 && barrels[i].zone == zone) {
barrels[i].flags.is_exist = 0;
return 1;
}
}
return 0;
}
#ifdef EMULATOR
void debug_print_robot_code(const struct robot_code *code, const short robot_id, int fd) {
// dprintf(fd, "INFO: code length is %d\n", cmd_index);
dprintf(fd, "Code for R%d, B%d:\n", robot_id, code->barrel_id);
for (int i = 0; i < 16; i++) {
const short cmd_arg = (short)(code->code[i] & (short)(~ROBOT_CMD_MASK));
if (code->PC == i) {
dprintf(fd, "==>%2d 0x%04X", i, code->code[i] & 0xFFFF);
} else {
dprintf(fd, " %2d 0x%04X", i, code->code[i] & 0xFFFF);
}
if ((code->code[i] & ROBOT_CMD_MASK) == ROBOT_CMD_END_code) {
dprintf(fd, " END\n");
break;
}
switch ((short)(code->code[i] & (short)ROBOT_CMD_MASK)) {
case ROBOT_CMD_MOVE_TO_ZONE_code:
if (cmd_arg & ROBOT_ZONE_PARKING) {
dprintf(fd, " move to parking (with barrel: %d)\n", (cmd_arg & ROBOT_WITH_BARREL) != 0);
} else {
dprintf(fd, " move to zone %d (with barrel: %d)\n",
cmd_arg & (~ROBOT_WITH_BARREL), (cmd_arg & ROBOT_WITH_BARREL) != 0);
}
break;
case ROBOT_CMD_MOVE_OFF_code:
dprintf(fd, " move to offset pos\n");
break;
case ROBOT_CMD_MOVE_ACCURATE_code:
dprintf(fd, " move to accurate pos\n");
break;
case ROBOT_CMD_UP_code:
dprintf(fd, " up (with barrel: %d)\n", (cmd_arg & ROBOT_WITH_BARREL) != 0);
break;
// в эмуляторе не важно где я, поэтому тут обе команды вниз обрабатываются одинаково
case ROBOT_CMD_DOWN_code:
dprintf(fd, " down (with barrel: %d)\n", (cmd_arg & ROBOT_WITH_BARREL) != 0);
break;
case ROBOT_CMD_WAIT_code:
dprintf(fd, " wait %d secs\n", cmd_arg);
break;
case ROBOT_CMD_TMR_SET_code:
dprintf(fd, " set barrel timer %d secs\n", cmd_arg);
break;
case ROBOT_CMD_SET_LOCK_ZONE_code:
dprintf(fd, " set lock zone %d\n", cmd_arg);
break;
case ROBOT_CMD_CORRECT_AXIS_code:
if (cmd_arg == ROBOT_AXIS_X) {
dprintf(fd, " correct axis: X\n");
} else if (cmd_arg == ROBOT_AXIS_Z) {
dprintf(fd, " correct axis: Z\n");
} else {
dprintf(fd, " correct axis: INVALID (%d)\n", cmd_arg);
}
break;
case ROBOT_CMD_INC_ZONE_code:
if (cmd_arg == ROBOT_ZONE_GAL) {
dprintf(fd, " increment zone: galvanic\n");
} else if (cmd_arg == ROBOT_ZONE_ETCH) {
dprintf(fd, " increment zone: etching\n");
} else {
dprintf(fd, " increment zone: INVALID (0x%4X)\n", cmd_arg);
}
break;
default:
dprintf(fd, " UNKNOWN: 0x%04X\n", code->code[i] & 0xFFFF);
}
}
}
#endif
/*
=== циклограмма перетаскивания барабана ===
// NOTE первой командой на любую транзакцию должна стоять команда опустить траверсу (в 22 зоне мы никогда не закончим, за нее не беспокоится)
если режим двух роботов:
если команда для робота 1:
robot1_lock_zone = min(зона изъятия, зона назначения)
иначе:
robot2_lock_zone = max(зона изъятия, зона назначения)
* опустить траверсу
если зона изъятия != промывка 3б:
если текущая зона != зона изъятия:
если зона изъятия == 22:
* встать на смещенную
* поднять траверсу
* уехать в 22 зону
* встать на смещенную
* опустить траверсу
* встать в точную
иначе:
* уехать в зону изъятия
если режим двух роботов:
* установить новую lock-зону
* поднять траверсу
если зона изъятия != 22 и зона изъятия >= 2:
* ждать скапывания (зависит от зоны)
* ехать в зону назначения
если зона назначения == 22:
* опустить траверсу
* уехать в 21 зону
если ЭНКОДЕРЫ СТАРЫЕ (по умолчанию):
* поднять траверсу
* опустить траверсу
если зона назначения != 22 и зона назначения != 0:
* установить время ожидания барабана (зависит от зоны)
иначе:
если текущая зона != промывка 3б:
* уехать в промывку 3б
* поднять траверсу
* уехать в пассивацию
* опустить траверсу
* ждать <время пассивации>
* поднять траверсу
* уехать в зону промывка 4а
* опустить траверсу
* установить время ожидания барабана (для промывки 4а)
*/
void create_operation(struct robot_code *code, short barrel_id, const short start_zone, const short dest_zone,
const short current_zone, const short robot_id) {
// создаем код транзакции, пока обычный
if (barrel_id >= BARRELS_COUNT) {
barrel_id = -1;
}
code->barrel_id = barrel_id;
if (barrel_id >= 0) {
barrels[barrel_id].flags.robot = robot_id;
}
short cmd_index = 0;
if (!one_robot_mode && !hla_night_mode) {
if (robot_id == 1) {
short tmp = dest_zone;
if (start_zone < dest_zone) {
tmp = start_zone;
}
if (tmp > ZONE_WASHING_3B) {
tmp = ZONE_WASHING_3B;
}
robot1_lock_zone = tmp;
} else {
short tmp = dest_zone;
if (start_zone > dest_zone) {
tmp = start_zone;
}
if (tmp < ZONE_WASHING_2A) {
tmp = ZONE_WASHING_2A;
}
robot2_lock_zone = tmp;
}
}
// если ночной режим, то нужно сразу обновить флаг у барабана
if (hla_night_mode && barrel_id >= 0) {
barrels[barrel_id].flags.is_night = -1;
}
// первым делом добавляем команду опустить траверсу
code->code[cmd_index++] = ROBOT_CMD_DOWN();
if (dest_zone == ZONE_PASSIVATION) {
// пассивация, тут все просто
if (current_zone != start_zone) {
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(ZONE_WASHING_3B);
}
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(ZONE_WASHING_4A);
code->code[cmd_index++] = ROBOT_CMD_UP_WITH_BARREL();
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE_WITH_BARREL(ZONE_PASSIVATION);
code->code[cmd_index++] = ROBOT_CMD_DOWN_WITH_BARREL();
// NOTE таймер робота работает точнее чем таймер барабанов
// code->code[cmd_index++] = ROBOT_CMD_TMR_SET(barrels[barrel_id].time_passivation);
code->code[cmd_index++] = ROBOT_CMD_WAIT(barrels[barrel_id].time_passivation);
code->code[cmd_index++] = ROBOT_CMD_UP_WITH_BARREL();
code->code[cmd_index++] = ROBOT_CMD_WAIT(hla_time_digging);
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE_WITH_BARREL(ZONE_WASHING_4A);
code->code[cmd_index++] = ROBOT_CMD_DOWN_WITH_BARREL();
code->code[cmd_index++] = ROBOT_CMD_TMR_SET(barrels[barrel_id].time_washing_4a);
} else {
// любой другой случай
if (current_zone != start_zone) {
if (start_zone != 22) {
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(start_zone);
} else {
code->code[cmd_index++] = ROBOT_CMD_MOVE_OFF();
code->code[cmd_index++] = ROBOT_CMD_UP();
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(22);
code->code[cmd_index++] = ROBOT_CMD_MOVE_OFF();
code->code[cmd_index++] = ROBOT_CMD_DOWN();
code->code[cmd_index++] = ROBOT_CMD_MOVE_ACCURATE();
}
}
// теперь обновляем LOCK-зону
if (!one_robot_mode && !hla_night_mode) {
if (robot_id == 1) {
// ставим lock-зону только если она ближе к концу линии
short tmp = dest_zone;
// lock-зона этого робота не может выходить за промывку 3А, потому что это не имеет смысла
if (tmp > ZONE_WASHING_3B) {
tmp = ZONE_WASHING_3B;
}
// в любом случае lock-зону нельзя двигать к началу линии
if (tmp > robot1_lock_zone) {
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(dest_zone);
}
} else {
// ставим lock-зону только если она ближе к началу линии
short tmp = dest_zone;
// lock-зона этого робота не может выходить за промывку 3А, потому что это не имеет смысла
if (tmp < ZONE_WASHING_2A) {
tmp = ZONE_WASHING_2A;
}
// в любом случае lock-зону нельзя двигать к концу линии
if (tmp < robot2_lock_zone) {
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(dest_zone);
}
}
}
code->code[cmd_index++] = ROBOT_CMD_UP_WITH_BARREL();
// теперь надо определиться с тем, сколько ждать скапывания
switch (start_zone) {
case ZONE_DEGREASING:
case ZONE_ETCHING_1:
case ZONE_ETCHING_2:
case ZONE_GALVANIZING_1:
case ZONE_GALVANIZING_2:
case ZONE_GALVANIZING_3:
case ZONE_GALVANIZING_4:
case ZONE_GALVANIZING_5:
case ZONE_GALVANIZING_6:
case ZONE_GALVANIZING_7:
case ZONE_GALVANIZING_8:
// время скапывания реактивов
code->code[cmd_index++] = ROBOT_CMD_WAIT(hla_time_reagent);
break;
case ZONE_WASHING_1A:
case ZONE_WASHING_2A:
case ZONE_WASHING_3A:
case ZONE_WASHING_4A:
// время скапывания первого каскада промывок
code->code[cmd_index++] = ROBOT_CMD_WAIT(hla_time_washing_1);
break;
case ZONE_WASHING_1B:
case ZONE_WASHING_2B:
case ZONE_WASHING_3B:
case ZONE_WASHING_4B:
// время скапывания второго каскада промывок
code->code[cmd_index++] = ROBOT_CMD_WAIT(hla_time_washing_2);
break;
}
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE_WITH_BARREL(dest_zone);
// инкремент зоны (если травление или цинкование)
if (dest_zone == ZONE_ETCHING_1 || dest_zone == ZONE_ETCHING_2) {
code->code[cmd_index++] = ROBOT_CMD_INC_ZONE(ROBOT_ZONE_ETCH);
} else if (dest_zone >= ZONE_GALVANIZING_1 && dest_zone <= ZONE_GALVANIZING_8) {
code->code[cmd_index++] = ROBOT_CMD_INC_ZONE(ROBOT_ZONE_GAL);
}
code->code[cmd_index++] = ROBOT_CMD_DOWN_WITH_BARREL();
if (dest_zone == 22) {
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(21);
// NOTE старая механика не позволяет просто опустить траверсу до конца, для новой изменить поведение
code->code[cmd_index++] = ROBOT_CMD_UP();
code->code[cmd_index++] = ROBOT_CMD_DOWN();
} else {
if (dest_zone != 0) {
// установка времени ожидания барабана
short tmp = -1;
switch (dest_zone) {
case ZONE_DEGREASING:
tmp = barrels[barrel_id].time_degreasing;
break;
case ZONE_ETCHING_1:
case ZONE_ETCHING_2:
tmp = barrels[barrel_id].time_etching;
break;
case ZONE_GALVANIZING_1:
case ZONE_GALVANIZING_2:
case ZONE_GALVANIZING_3:
case ZONE_GALVANIZING_4:
case ZONE_GALVANIZING_5:
case ZONE_GALVANIZING_6:
case ZONE_GALVANIZING_7:
case ZONE_GALVANIZING_8:
tmp = barrels[barrel_id].time_galvanizing;
break;
case ZONE_WASHING_1A:
tmp = barrels[barrel_id].time_washing_1a;
break;
case ZONE_WASHING_2A:
tmp = barrels[barrel_id].time_washing_2a;
break;
case ZONE_WASHING_3A:
tmp = barrels[barrel_id].time_washing_3a;
break;
case ZONE_WASHING_4A:
tmp = barrels[barrel_id].time_washing_4a;
break;
case ZONE_WASHING_1B:
tmp = barrels[barrel_id].time_washing_1b;
break;
case ZONE_WASHING_2B:
tmp = barrels[barrel_id].time_washing_2b;
break;
case ZONE_WASHING_3B:
tmp = barrels[barrel_id].time_washing_3b;
break;
case ZONE_WASHING_4B:
tmp = barrels[barrel_id].time_washing_4b;
break;
}
if (tmp > 0) {
if (tmp > 8000) {
tmp = 8000;
}
code->code[cmd_index++] = ROBOT_CMD_TMR_SET(tmp);
}
}
}
}
if (!one_robot_mode && !hla_night_mode) {
if (robot_id == 2) {
if (dest_zone >= ZONE_GALVANIZING_1) {
// из промывки 2б он перекладывал, пусть едет в промывку 2а
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(ZONE_WASHING_2A);
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(ZONE_WASHING_2A);
} else if (dest_zone < ZONE_DEGREASING) {
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(ZONE_DEGREASING);
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(ZONE_DEGREASING);
}
} else if (robot_id == 1) {
if (dest_zone <= ZONE_WASHING_3A) {
// чтобы из этой зоны можно было переложить барабан первому роботу
code->code[cmd_index++] = ROBOT_CMD_SET_LOCK_ZONE(ZONE_WASHING_4A);
code->code[cmd_index++] = ROBOT_CMD_MOVE_TO_ZONE(ZONE_WASHING_4A);
}
}
}
code->code[cmd_index++] = ROBOT_CMD_END();
#ifdef EMULATOR
printf("INFO: code length is %d\n", cmd_index);
debug_print_robot_code(code, robot_id, 0);
#endif
code->PC = 0;
}