// // Created by Владислав Остапов on 13.12.2022. // #ifdef EMULATOR #include #include "emulator.h" #else #include "main.h" #endif #define LOCK_ZONE_BORDER 1 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; } // 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) { // задача - найти первую свободную промывку const short zones[] = { 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 }; // всего зон, куда можно сныкать барабаны 9 (8 промывок и выгрузка) for (short i = 0; i < 9; i++) { if (!zone_is_busy(zones[i])) { return zones[i]; } } return -1; } else { // если барабан ночной, то надо проверить выгрузку (туда выгружаются ночные барабаны) if (bar->flags.is_night) { if (!zone_is_busy(ZONE_LOAD_1)) { return ZONE_LOAD_1; } return -1; } // дальше нужно проверить, что можно передвигать бочку short dest_zone = -1; 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 свободна, то диапазон начнется с 3Б 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: 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, const short barrel_id, const short start_zone, const short dest_zone, const short current_zone, const short robot_id) { // создаем код транзакции, пока обычный code->barrel_id = barrel_id; short cmd_index = 0; if (!one_robot_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_2B) { tmp = ZONE_WASHING_2B; } robot2_lock_zone = tmp; } } // если ночной режим, то нужно сразу обновить флаг у барабана if (hla_night_mode && barrel_id >= 0 && barrel_id < BARRELS_COUNT) { 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_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) { 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_2B) { tmp = ZONE_WASHING_2B; } // в любом случае 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) { 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; }