| | |
| | | continue; |
| | | } |
| | | |
| | | // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。 |
| | | auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool { |
| | | if (m_schedulingMode != SchedulingMode::Production) return true; |
| | | const bool isG1Port = (portIndex == 0 || portIndex == 2); |
| | | if (type == MaterialsType::G1) return isG1Port; |
| | | if (type == MaterialsType::G2) return !isG1Port; |
| | | return true; |
| | | }; |
| | | |
| | | // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。 |
| | | auto getPreferredPortForType = [&](MaterialsType targetType) -> int { |
| | | if (m_schedulingMode != SchedulingMode::Production) return -1; |
| | | |
| | | auto preferG1ByG2 = [&](CGlass* pG2) -> int { |
| | | if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1; |
| | | int originPort = -1, originSlot = -1; |
| | | pG2->getOrginPort(originPort, originSlot); |
| | | if (originPort == 1) return 0; // Port2 -> Port1 |
| | | if (originPort == 3) return 2; // Port4 -> Port3 |
| | | return -1; |
| | | }; |
| | | auto preferG2ByG1 = [&](CGlass* pG1) -> int { |
| | | if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1; |
| | | int originPort = -1, originSlot = -1; |
| | | pG1->getOrginPort(originPort, originSlot); |
| | | if (originPort == 0) return 1; // Port1 -> Port2 |
| | | if (originPort == 2) return 3; // Port3 -> Port4 |
| | | return -1; |
| | | }; |
| | | |
| | | if (targetType == MaterialsType::G1) { |
| | | int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pFliper->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | } |
| | | else if (targetType == MaterialsType::G2) { |
| | | int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。 |
| | | auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool { |
| | | if (m_pActiveRobotTask == nullptr) return false; |
| | | CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext(); |
| | | CGlass* pExistingG2 = pBonder->getGlassFromSlot(0); |
| | | if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true; |
| | | |
| | | CProcessJob* pjG1 = pIncomingG1->getProcessJob(); |
| | | CProcessJob* pjG2 = pExistingG2->getProcessJob(); |
| | | if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) { |
| | | std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id(); |
| | | std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id(); |
| | | LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。", |
| | | bonderName, pj1.c_str(), pj2.c_str()); |
| | | delete m_pActiveRobotTask; |
| | | m_pActiveRobotTask = nullptr; |
| | | return false; |
| | | } |
| | | |
| | | // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。 |
| | | if (m_schedulingMode == SchedulingMode::Production) { |
| | | int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0; |
| | | pIncomingG1->getOrginPort(g1Port, g1Slot); |
| | | pExistingG2->getOrginPort(g2Port, g2Slot); |
| | | const bool pairOk = |
| | | (g1Port == 0 && g2Port == 1) || |
| | | (g1Port == 2 && g2Port == 3); |
| | | if (!pairOk) { |
| | | LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。", |
| | | bonderName, g1Port + 1, g2Port + 1); |
| | | delete m_pActiveRobotTask; |
| | | m_pActiveRobotTask = nullptr; |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | |
| | | // Bonder1、Bonder2、Fliper、VacuumBake、Aligner,统计G2和G1的数量, 配对组数, 多出的类型 |
| | | int nG2Count = 0, nG1Count = 0, nGlassGroup, nExtraType; |
| | |
| | | // VacuumBake(G1) -> Bonder |
| | | if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) { |
| | | m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0); |
| | | if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) { |
| | | // 同 PJ 校验失败,本轮不下发搬送 |
| | | } |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | |
| | | if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) { |
| | | m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0); |
| | | if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) { |
| | | // 同 PJ 校验失败,本轮不下发搬送 |
| | | } |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | |
| | |
| | | else { |
| | | primaryType = MaterialsType::G1; |
| | | } |
| | | const int preferredPortForPrimary = getPreferredPortForType(primaryType); |
| | | { |
| | | static int s_prevPrimaryType = -1; |
| | | static int s_prevPreferredPort = -2; |
| | | const int curPrimaryType = (int)primaryType; |
| | | if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) { |
| | | LOGI("<Master>LoadPort->Aligner规则(RUNNING): primaryType=%d, preferredPort=%d", |
| | | curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); |
| | | s_prevPrimaryType = curPrimaryType; |
| | | s_prevPreferredPort = preferredPortForPrimary; |
| | | } |
| | | } |
| | | |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Loading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | if (!isProductionPortTypeMatch(s, primaryType)) { |
| | | continue; |
| | | } |
| | | if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) { |
| | | continue; |
| | | } |
| | | m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode); |
| | | if (m_pActiveRobotTask != nullptr) { |
| | | LOGI("<Master>LoadPort->Aligner命中(RUNNING): port=%d, primaryType=%d, preferredPort=%d", |
| | | s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); |
| | | CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext(); |
| | | if (pGlass->getBuddy() != nullptr) { |
| | | delete m_pActiveRobotTask; |
| | |
| | | unlock(); // 等当前任务完成或中止后继续 |
| | | continue; |
| | | } |
| | | |
| | | // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。 |
| | | auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool { |
| | | if (m_schedulingMode != SchedulingMode::Production) return true; |
| | | const bool isG1Port = (portIndex == 0 || portIndex == 2); |
| | | if (type == MaterialsType::G1) return isG1Port; |
| | | if (type == MaterialsType::G2) return !isG1Port; |
| | | return true; |
| | | }; |
| | | |
| | | // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。 |
| | | auto getPreferredPortForType = [&](MaterialsType targetType) -> int { |
| | | if (m_schedulingMode != SchedulingMode::Production) return -1; |
| | | |
| | | auto preferG1ByG2 = [&](CGlass* pG2) -> int { |
| | | if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1; |
| | | int originPort = -1, originSlot = -1; |
| | | pG2->getOrginPort(originPort, originSlot); |
| | | if (originPort == 1) return 0; // Port2 -> Port1 |
| | | if (originPort == 3) return 2; // Port4 -> Port3 |
| | | return -1; |
| | | }; |
| | | auto preferG2ByG1 = [&](CGlass* pG1) -> int { |
| | | if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1; |
| | | int originPort = -1, originSlot = -1; |
| | | pG1->getOrginPort(originPort, originSlot); |
| | | if (originPort == 0) return 1; // Port1 -> Port2 |
| | | if (originPort == 2) return 3; // Port3 -> Port4 |
| | | return -1; |
| | | }; |
| | | |
| | | if (targetType == MaterialsType::G1) { |
| | | int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pFliper->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG1ByG2(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | } |
| | | else if (targetType == MaterialsType::G2) { |
| | | int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p; |
| | | p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。 |
| | | auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool { |
| | | if (m_pActiveRobotTask == nullptr) return false; |
| | | CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext(); |
| | | CGlass* pExistingG2 = pBonder->getGlassFromSlot(0); |
| | | if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true; |
| | | |
| | | CProcessJob* pjG1 = pIncomingG1->getProcessJob(); |
| | | CProcessJob* pjG2 = pExistingG2->getProcessJob(); |
| | | if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) { |
| | | std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id(); |
| | | std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id(); |
| | | LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。", |
| | | bonderName, pj1.c_str(), pj2.c_str()); |
| | | delete m_pActiveRobotTask; |
| | | m_pActiveRobotTask = nullptr; |
| | | return false; |
| | | } |
| | | |
| | | // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。 |
| | | if (m_schedulingMode == SchedulingMode::Production) { |
| | | int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0; |
| | | pIncomingG1->getOrginPort(g1Port, g1Slot); |
| | | pExistingG2->getOrginPort(g2Port, g2Slot); |
| | | const bool pairOk = |
| | | (g1Port == 0 && g2Port == 1) || |
| | | (g1Port == 2 && g2Port == 3); |
| | | if (!pairOk) { |
| | | LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。", |
| | | bonderName, g1Port + 1, g2Port + 1); |
| | | delete m_pActiveRobotTask; |
| | | m_pActiveRobotTask = nullptr; |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // 5.5) 暂停状态检查:若 CJ 或在制 PJ 处于 Paused,暂缓调度新的搬送 |
| | | bool pausedByEvent = false; |
| | |
| | | // 13) VacuumBake(G1) -> Bonder(槽级判定:slot0(G2) 已有且 slot1(G1) 为空) |
| | | if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) { |
| | | m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0); |
| | | if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) { |
| | | // 同 PJ 校验失败,本轮不下发搬送 |
| | | } |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) { |
| | | m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0); |
| | | if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) { |
| | | // 同 PJ 校验失败,本轮不下发搬送 |
| | | } |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | |
| | |
| | | |
| | | // 16) LoadPort -> Aligner(受组数门限控制;统一 buddy/状态时序) |
| | | if (blockLoadFromLP) { unlock(); continue; } |
| | | const int preferredPortForPrimary = getPreferredPortForType(primaryType); |
| | | { |
| | | static int s_prevPrimaryType = -1; |
| | | static int s_prevPreferredPort = -2; |
| | | const int curPrimaryType = (int)primaryType; |
| | | if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) { |
| | | LOGI("<Master>LoadPort->Aligner规则(RUNNING_BATCH): primaryType=%d, preferredPort=%d", |
| | | curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); |
| | | s_prevPrimaryType = curPrimaryType; |
| | | s_prevPreferredPort = preferredPortForPrimary; |
| | | } |
| | | } |
| | | |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Loading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | if (!isProductionPortTypeMatch(s, primaryType)) { |
| | | continue; |
| | | } |
| | | if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) { |
| | | continue; |
| | | } |
| | | |
| | | m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode); |
| | | if (m_pActiveRobotTask != nullptr) { |
| | | LOGI("<Master>LoadPort->Aligner命中(RUNNING_BATCH): port=%d, primaryType=%d, preferredPort=%d", |
| | | s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); |
| | | auto* pGlass = static_cast<CGlass*>(m_pActiveRobotTask->getContext()); |
| | | if (pGlass->getBuddy() != nullptr) { |
| | | delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr; |
| | |
| | | pGlass->setProcessJob(pj); |
| | | |
| | | PJWarp& jpWarp = pj->getPjWarp(); |
| | | int portIndex = -1; |
| | | switch (pPort->getID()) { |
| | | case EQ_ID_LOADPORT1: portIndex = 0; break; |
| | | case EQ_ID_LOADPORT2: portIndex = 1; break; |
| | | case EQ_ID_LOADPORT3: portIndex = 2; break; |
| | | case EQ_ID_LOADPORT4: portIndex = 3; break; |
| | | default: break; |
| | | } |
| | | |
| | | BOOL scheduled = FALSE; |
| | | int material = 1; // G1 |
| | | if (1 <= slot && slot <= 8) { |
| | | if (0 <= portIndex && portIndex <= 3 && jpWarp.selectedPorts[portIndex]) { |
| | | scheduled = jpWarp.checkSlots[portIndex][slot - 1]; |
| | | material = jpWarp.materialSlots[portIndex][slot - 1]; |
| | | } |
| | | else { |
| | | // Backward compatibility for legacy single-port PJWarp. |
| | | scheduled = jpWarp.checkSlot[slot - 1]; |
| | | material = jpWarp.material[slot - 1]; |
| | | } |
| | | } |
| | | material = (material == 2) ? 2 : 1; |
| | | |
| | | int nRecipeID = RecipeManager::getInstance().getIdByPPID(pj->recipeSpec()); |
| | | RecipeInfo stRecipeInfo = RecipeManager::getInstance().getRecipeByPPID(pj->recipeSpec()); |
| | | std::vector<DeviceRecipe> vecRecipeInfo = stRecipeInfo.vecDeviceList; |
| | | |
| | | pGlass->setScheduledForProcessing(jpWarp.checkSlot[slot-1]); |
| | | pGlass->setType(static_cast<SERVO::MaterialsType>(jpWarp.material[slot-1])); |
| | | pGlass->setScheduledForProcessing(scheduled); |
| | | pGlass->setType(static_cast<SERVO::MaterialsType>(material)); |
| | | |
| | | SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS(); |
| | | if (pJobDataS != nullptr) { |
| | |
| | | pJobDataS->setLotId(pj->getLotId().c_str()); |
| | | pJobDataS->setProductId(pj->getProductId().c_str()); |
| | | pJobDataS->setOperationId(pj->getOperationId().c_str()); |
| | | pJobDataS->setMaterialsType(jpWarp.material[slot - 1]); |
| | | pJobDataS->setMaterialsType(material); |
| | | pJobDataS->setMasterRecipe(nRecipeID); |
| | | for (const auto& info : vecRecipeInfo) { |
| | | const std::string& name = info.strDeviceName; |