| | |
| | | #include "CMaster.h" |
| | | #include <future> |
| | | #include <vector> |
| | | #include <algorithm> |
| | | #include "RecipeManager.h" |
| | | #include <fstream> |
| | | #include "SerializeUtil.h" |
| | | #include "CServoUtilsTool.h" |
| | | #include "AlarmManager.h" |
| | | #include "ToolUnits.h" |
| | | #include "Model.h" |
| | | |
| | | |
| | | namespace SERVO { |
| | |
| | | m_ullStartTime = 0; |
| | | m_ullRunTime = 0; |
| | | m_state = MASTERSTATE::READY; |
| | | m_curveMode = CurveMode::Production; |
| | | m_schedulingMode = SchedulingMode::Production; |
| | | m_pActiveRobotTask = nullptr; |
| | | m_nLastError = ER_CODE_NOERROR; |
| | | m_isCompareMapsBeforeProceeding = FALSE; |
| | |
| | | m_nContinuousWorkingPort = 0; |
| | | m_nContinuousWorkingSlot = 0; |
| | | m_pControlJob = nullptr; |
| | | m_bPauseAlarmRaised = false; |
| | | m_pModelCtx = nullptr; |
| | | m_nTestFlag = 0; |
| | | InitializeCriticalSection(&m_criticalSection); |
| | | } |
| | |
| | | m_hEventDispatchThreadExit[1] = nullptr; |
| | | } |
| | | |
| | | |
| | | DeleteCriticalSection(&m_criticalSection); |
| | | } |
| | | |
| | | void CMaster::setListener(MasterListener listener) |
| | | { |
| | | m_listener = listener; |
| | | } |
| | | |
| | | void CMaster::setModelCtx(CModel* pModel) |
| | | { |
| | | m_pModelCtx = pModel; |
| | | } |
| | | |
| | | CRobotTask* CMaster::getActiveRobotTask() |
| | |
| | | |
| | | int CMaster::init() |
| | | { |
| | | const ULONGLONG boot_master_begin = GetTickCount64(); |
| | | LOGI("<Master>正在初始化..."); |
| | | LOGI("[BOOT][MASTER] init begin"); |
| | | |
| | | |
| | | // cclink |
| | | if (m_cclink.Connect(CC_LINK_IE_CONTROL_CHANNEL(1)) != 0) { |
| | | const ULONGLONG boot_cclink_begin = GetTickCount64(); |
| | | const int cc_ret = m_cclink.Connect(CC_LINK_IE_CONTROL_CHANNEL(1)); |
| | | LOGI("[BOOT][MASTER] CC-Link connect ret=%d, cost=%llu ms", |
| | | cc_ret, |
| | | (unsigned long long)(GetTickCount64() - boot_cclink_begin)); |
| | | if (cc_ret != 0) { |
| | | LOGE("连接CC-Link失败."); |
| | | } |
| | | else { |
| | |
| | | |
| | | |
| | | // 读缓存数据 |
| | | const ULONGLONG boot_cache_begin = GetTickCount64(); |
| | | const ULONGLONG boot_read_begin = GetTickCount64(); |
| | | readCache(); |
| | | LOGI("[BOOT][MASTER] readCache finished, cost=%llu ms", (unsigned long long)(GetTickCount64() - boot_read_begin)); |
| | | |
| | | const ULONGLONG boot_state_begin = GetTickCount64(); |
| | | loadState(); |
| | | LOGI("[BOOT][MASTER] loadState finished, cost=%llu ms", (unsigned long long)(GetTickCount64() - boot_state_begin)); |
| | | if (m_listener.onControlJobChanged) { |
| | | m_listener.onControlJobChanged(this); |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | LOGI("[BOOT][MASTER] cache/state loaded, cost=%llu ms (since init %llu ms)", |
| | | (unsigned long long)(GetTickCount64() - boot_cache_begin), |
| | | (unsigned long long)(GetTickCount64() - boot_master_begin)); |
| | | |
| | | |
| | | // 定时器 |
| | |
| | | |
| | | |
| | | LOGI("<Master>初始化完成."); |
| | | LOGI("[BOOT][MASTER] init finished, total cost=%llu ms", |
| | | (unsigned long long)(GetTickCount64() - boot_master_begin)); |
| | | return 0; |
| | | } |
| | | |
| | | void CMaster::setCurveMode(CurveMode mode) |
| | | { |
| | | if (m_curveMode == mode) { |
| | | return; |
| | | } |
| | | m_curveMode = mode; |
| | | if (m_pCollector != nullptr) { |
| | | const uint32_t mids[] = { |
| | | MID_Bonder1, MID_Bonder2, |
| | | MID_VacuumBakeA, MID_VacuumBakeB, |
| | | MID_BakeCoolingA, MID_BakeCoolingB |
| | | }; |
| | | for (uint32_t mid : mids) { |
| | | if (mode == CurveMode::EmptyChamber) { |
| | | m_pCollector->batchStart(mid, "EMPTY_CHAMBER", 30 * 60 * 1000ULL); // 空腔模式:启动采样批次 |
| | | } |
| | | else { |
| | | m_pCollector->batchStop(mid); |
| | | m_pCollector->buffersClear(mid); // 切回生产模式,清掉空腔数据 |
| | | } |
| | | } |
| | | } |
| | | LOGI("<Master>CurveMode=%s", mode == CurveMode::EmptyChamber ? "EmptyChamber" : "Production"); |
| | | } |
| | | |
| | | CurveMode CMaster::getCurveMode() const |
| | | { |
| | | return m_curveMode; |
| | | } |
| | | |
| | | void CMaster::setSchedulingMode(SchedulingMode mode) |
| | | { |
| | | m_schedulingMode = mode; |
| | | LOGI("<Master>SchedulingMode=%s", mode == SchedulingMode::Production ? "Production" : "Tuning"); |
| | | } |
| | | |
| | | SchedulingMode CMaster::getSchedulingMode() const |
| | | { |
| | | return m_schedulingMode; |
| | | } |
| | | |
| | | int CMaster::term() |
| | |
| | | } |
| | | m_listEquipment.clear(); |
| | | |
| | | // release manual-remove buffer before glass pool is torn down |
| | | for (auto* pGlass : m_bufGlass) { |
| | | if (pGlass != nullptr) { |
| | | pGlass->release(); |
| | | } |
| | | } |
| | | m_bufGlass.clear(); |
| | | |
| | | if (m_pCollector != nullptr) { |
| | | m_pCollector->stopLoop(); |
| | |
| | | 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; |
| | |
| | | |
| | | |
| | | // Measurement -> LoadPort |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Unloading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | m_pActiveRobotTask = createTransferTask(pMeasurement, pLoadPorts[s], MaterialsType::G1, secondaryType); |
| | | if (m_pActiveRobotTask != nullptr) { |
| | | goto PORT_PUT; |
| | | if (m_schedulingMode == SchedulingMode::Production) { |
| | | if (!rmd.armState[0]) { |
| | | m_pActiveRobotTask = createTransferTask_returnOrigin(pMeasurement, pLoadPorts); |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | } |
| | | else { |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Unloading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | m_pActiveRobotTask = createTransferTask(pMeasurement, pLoadPorts[s], MaterialsType::G1, secondaryType); |
| | | if (m_pActiveRobotTask != nullptr) { |
| | | goto PORT_PUT; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | // 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; |
| | |
| | | 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; |
| | | if (m_pControlJob != nullptr && m_pControlJob->state() == CJState::Paused) { |
| | | pausedByEvent = true; |
| | | } |
| | | for (auto pj : m_inProcesJobs) { |
| | | if (pj != nullptr && pj->state() == PJState::Paused) { |
| | | pausedByEvent = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!pausedByEvent && m_bPauseAlarmRaised) { |
| | | if (m_pModelCtx != nullptr) { |
| | | m_pModelCtx->clearSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, 0, 0); |
| | | } |
| | | else { |
| | | AlarmManager& alarmManager = AlarmManager::getInstance(); |
| | | alarmManager.clearAlarmByAttributes(ALID_SOFTWARE_PAUSE_EVENT, 0, 0, CToolUnits::getCurrentTimeString()); |
| | | } |
| | | m_bPauseAlarmRaised = false; |
| | | } |
| | | if (pausedByEvent) { |
| | | LOGI("<Master>调度暂停:ControlJob/ProcessJob 处于 Paused 状态(可能由 PauseEvent 触发)"); |
| | | unlock(); |
| | | continue; |
| | | } |
| | | |
| | | // 6) ——关键:全局统计 G1/G2 与组数门限(与单片分支对齐)—— |
| | | auto countG1G2 = [&]() { |
| | | int g1 = 0, g2 = 0; |
| | |
| | | // 组数门限:≥2 组时不再从 LP 上片,避免堆积(与单片一致) |
| | | bool blockLoadFromLP = (nGlassGroup >= 2); |
| | | |
| | | // 7) Measurement -> LoadPort(固定:G1 优先回 LP) |
| | | // 7) Measurement -> LoadPort |
| | | if (rmd.armState[0] || rmd.armState[1]) { |
| | | LOGD("Arm1 %s, Arm2 %s.", |
| | | rmd.armState[0] ? _T("不可用") : _T("可用"), |
| | | rmd.armState[1] ? _T("不可用") : _T("可用")); |
| | | } |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Unloading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | m_pActiveRobotTask = createTransferTask(pMeasurement, pLoadPorts[s], MaterialsType::G1, secondaryType); |
| | | if (m_pActiveRobotTask != nullptr) { goto BATCH_PORT_PUT; } |
| | | if (m_schedulingMode == SchedulingMode::Production) { |
| | | if (!rmd.armState[0]) { |
| | | m_pActiveRobotTask = createTransferTask_returnOrigin(pMeasurement, pLoadPorts); |
| | | CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); |
| | | } |
| | | } |
| | | else { |
| | | for (int s = 0; s < 4; s++) { |
| | | PortType pt = pLoadPorts[s]->getPortType(); |
| | | if (!rmd.armState[0] && pLoadPorts[s]->isEnable() |
| | | && (pt == PortType::Unloading || pt == PortType::Both) |
| | | && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { |
| | | m_pActiveRobotTask = createTransferTask(pMeasurement, pLoadPorts[s], MaterialsType::G1, secondaryType); |
| | | if (m_pActiveRobotTask != nullptr) { goto BATCH_PORT_PUT; } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | // 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; |
| | |
| | | } |
| | | }; |
| | | listener.onSVDataReport = [&](void* pEquipment, void* pData) { |
| | | const bool allowSvLog = |
| | | (m_state == MASTERSTATE::RUNNING || |
| | | m_state == MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER || |
| | | m_state == MASTERSTATE::RUNNING_BATCH || |
| | | m_state == MASTERSTATE::STARTING); |
| | | const bool allowCurve = allowSvLog || (m_curveMode == CurveMode::EmptyChamber); |
| | | if (!allowCurve) { |
| | | return; |
| | | } |
| | | CSVData* pSVData = (CSVData*)pData; |
| | | auto rawData = pSVData->getSVRawData(); |
| | | std::vector<CParam> params; |
| | |
| | | int paramIndex = mapping.first; |
| | | int channel = mapping.second; |
| | | |
| | | if (paramIndex < params.size() && channel - 1 < vacuumbakeTypes.size()) { |
| | | if (paramIndex < params.size()) { |
| | | auto& param = params.at(paramIndex); |
| | | double value = param.getDoubleValue(); |
| | | const std::string& dataType = vacuumbakeTypes[channel - 1]; |
| | | const std::string& paramName = param.getName(); |
| | | const char slotTag = !paramName.empty() ? paramName[0] : '\0'; |
| | | const int typeIndex = (slotTag == 'B') ? (channel - 8) : (channel - 1); |
| | | if (typeIndex < 0 || typeIndex >= (int)vacuumbakeTypes.size()) { |
| | | continue; |
| | | } |
| | | const int pushChannel = typeIndex + 1; |
| | | const std::string& dataType = vacuumbakeTypes[typeIndex]; |
| | | |
| | | if (m_pCollector != nullptr) { |
| | | if (slotTag == 'A') |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 1), channel, ts, value); |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 1), pushChannel, ts, value); |
| | | else if (slotTag == 'B') |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 2), channel, ts, value); |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 2), pushChannel, ts, value); |
| | | } |
| | | |
| | | // 根据腔体前缀写入对应 Slot 的玻璃 |
| | |
| | | int paramIndex = mapping.first; |
| | | int channel = mapping.second; |
| | | |
| | | if (paramIndex < params.size() && channel - 1 < coolingTypes.size()) { |
| | | if (paramIndex < params.size()) { |
| | | auto& param = params.at(paramIndex); |
| | | double value = param.getDoubleValue(); |
| | | const std::string& dataType = coolingTypes[channel - 1]; |
| | | const std::string& paramName = param.getName(); |
| | | const char slotTag = !paramName.empty() ? paramName[0] : '\0'; |
| | | const bool paramIsBake = paramName.find("烘烤") != std::string::npos; |
| | | const bool paramIsCooling = paramName.find("冷却") != std::string::npos; |
| | | const int typeIndex = (slotTag == 'B') ? (channel - 7) : (channel - 1); |
| | | if (typeIndex < 0 || typeIndex >= (int)coolingTypes.size()) { |
| | | continue; |
| | | } |
| | | const int pushChannel = typeIndex + 1; |
| | | const std::string& dataType = coolingTypes[typeIndex]; |
| | | |
| | | if (m_pCollector != nullptr && paramIsBake) { |
| | | if (slotTag == 'A') |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 1), channel, ts, value); |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 1), pushChannel, ts, value); |
| | | else if (slotTag == 'B') |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 3), channel, ts, value); |
| | | m_pCollector->buffersPush(SlotToMid(eqid, 3), pushChannel, ts, value); |
| | | } |
| | | |
| | | if (!dataType.empty()) { |
| | |
| | | } |
| | | } |
| | | |
| | | }; |
| | | listener.onReceivedJob = [&](void* pEquipment, int port, CJobDataS* pJobDataS) { |
| | | if (m_listener.onJobReceived != nullptr) { |
| | | m_listener.onJobReceived(this, (CEquipment*)pEquipment, port, pJobDataS); |
| | | } |
| | | }; |
| | | listener.onSentOutJob = [&](void* pEquipment, int port, CJobDataS* pJobDataS) { |
| | | if (m_listener.onJobSentOut != nullptr) { |
| | | m_listener.onJobSentOut(this, (CEquipment*)pEquipment, port, pJobDataS); |
| | | } |
| | | }; |
| | | listener.onEqStatusChanged = [&](void* pEquipment, int unitId, int status, int reason) { |
| | | if (m_listener.onEqStatusChanged != nullptr) { |
| | | m_listener.onEqStatusChanged(this, (CEquipment*)pEquipment, unitId, status, reason); |
| | | } |
| | | }; |
| | | pEquipment->setListener(listener); |
| | | pEquipment->setCcLink(&m_cclink); |
| | |
| | | fireLoadPortStatus(PORT_LOAD_READY); |
| | | fireLoadPortStatus(PORT_EMPTY); // will also raise LoadPortNotAssoc via Model |
| | | break; |
| | | case 24: { // 模拟 SV Data(示例:Bonder1) |
| | | SERVO::CEquipment* pEq = getEquipment(EQ_ID_Bonder1); |
| | | if (pEq != nullptr && m_listener.onSVDataReport != nullptr) { |
| | | static int counter = 0; |
| | | ++counter; |
| | | std::vector<CParam> params; |
| | | params.emplace_back("MockSV_Temp", "1", "C", 25 + (counter % 5)); |
| | | params.emplace_back("MockSV_Pressure", "2", "kPa", 100 + (counter % 3)); |
| | | params.emplace_back("MockSV_Speed", "3", "mm/s", 50 + (counter % 7)); |
| | | m_listener.onSVDataReport(this, pEq, params); |
| | | LOGI("<Master>SIM_EAP step24: mock SVData (Bonder1), params=%zu", params.size()); |
| | | } |
| | | break; |
| | | } |
| | | case 25: { // 模拟 Process Data(示例:Bonder1) |
| | | SERVO::CEquipment* pEq = getEquipment(EQ_ID_Bonder1); |
| | | if (pEq != nullptr && m_listener.onProcessDataReport != nullptr) { |
| | | static int counter = 0; |
| | | ++counter; |
| | | std::vector<CParam> params; |
| | | params.emplace_back("MockProc_CycleTime", "1", "s", 30 + (counter % 4)); |
| | | params.emplace_back("MockProc_MaxTemp", "2", "C", 200 + (counter % 6)); |
| | | params.emplace_back("MockProc_Result", "3", "", (counter % 2) ? 1 : 0); |
| | | m_listener.onProcessDataReport(this, pEq, params); |
| | | LOGI("<Master>SIM_EAP step25: mock ProcessData (Bonder1), params=%zu", params.size()); |
| | | } |
| | | break; |
| | | } |
| | | default: |
| | | break; |
| | | } |
| | |
| | | |
| | | nRet = pLoadPort1->getPin("Out")->connectPin(pAligner->getPin("In1")); |
| | | if (nRet < 0) { |
| | | LOGE("连接LoadPort1-Fliper失败"); |
| | | LOGE("连接LoadPort1-Aligner失败"); |
| | | } |
| | | nRet = pLoadPort2->getPin("Out")->connectPin(pAligner->getPin("In2")); |
| | | if (nRet < 0) { |
| | | LOGE("连接LoadPort1-Fliper失败"); |
| | | LOGE("连接LoadPort2-Aligner失败"); |
| | | } |
| | | nRet = pLoadPort3->getPin("Out")->connectPin(pAligner->getPin("In3")); |
| | | if (nRet < 0) { |
| | | LOGE("连接LoadPort3-Aligner失败"); |
| | | } |
| | | nRet = pLoadPort4->getPin("Out")->connectPin(pAligner->getPin("In4")); |
| | | if (nRet < 0) { |
| | | LOGE("连接LoadPort4-Aligner失败"); |
| | | } |
| | | |
| | | nRet = pAligner->getPin("Out1")->connectPin(pFliper->getPin("In")); |
| | |
| | | LOGE("连接BakeCooling-LoadPort3失败"); |
| | | } |
| | | |
| | | nRet = pMeasurement->getPin("Out1")->connectPin(pLoadPort3->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接BakeCooling-LoadPort3失败"); |
| | | } |
| | | if (m_schedulingMode == SchedulingMode::Production) { |
| | | // 生产模式:测量输出回到 G1 原位(默认 Port1 / Port3) |
| | | nRet = pMeasurement->getPin("Out1")->connectPin(pLoadPort1->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接Measurement-LoadPort1失败"); |
| | | } |
| | | |
| | | nRet = pMeasurement->getPin("Out2")->connectPin(pLoadPort4->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接BakeCooling-LoadPort4失败"); |
| | | nRet = pMeasurement->getPin("Out2")->connectPin(pLoadPort3->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接Measurement-LoadPort3失败"); |
| | | } |
| | | } |
| | | else { |
| | | // 调机模式:维持原连接(Out1->Port3, Out2->Port4) |
| | | nRet = pMeasurement->getPin("Out1")->connectPin(pLoadPort3->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接Measurement-LoadPort3失败"); |
| | | } |
| | | |
| | | nRet = pMeasurement->getPin("Out2")->connectPin(pLoadPort4->getPin("In")); |
| | | if (nRet < 0) { |
| | | LOGE("连接Measurement-LoadPort4失败"); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | return pTask; |
| | | } |
| | | |
| | | CRobotTask* CMaster::createTransferTask_returnOrigin(CEquipment* pEqSrc, CLoadPort** pPorts) |
| | | { |
| | | if (!pEqSrc->IsEnabled()) { |
| | | return nullptr; |
| | | } |
| | | |
| | | CSlot* pSrcSlot = pEqSrc->getProcessedSlot(MaterialsType::G1, m_bJobMode); |
| | | if (pSrcSlot == nullptr) { |
| | | return nullptr; |
| | | } |
| | | |
| | | CGlass* pGlass = (CGlass*)pSrcSlot->getContext(); |
| | | if (pGlass == nullptr) { |
| | | return nullptr; |
| | | } |
| | | |
| | | int port = 0, slot = 0; |
| | | pGlass->getOrginPort(port, slot); |
| | | if (port < 0 || port >= 4 || slot < 0 || slot >= SLOT_MAX) { |
| | | return nullptr; |
| | | } |
| | | |
| | | CLoadPort* pPort = pPorts[port]; |
| | | if (pPort == nullptr || !pPort->isEnable()) { |
| | | return nullptr; |
| | | } |
| | | PortType pt = pPort->getPortType(); |
| | | if (!(pt == PortType::Unloading || pt == PortType::Both)) { |
| | | return nullptr; |
| | | } |
| | | if (pPort->getPortStatus() != PORT_INUSE) { |
| | | return nullptr; |
| | | } |
| | | |
| | | CSlot* pTarSlot = pPort->getSlot(slot); |
| | | if (pTarSlot == nullptr) { |
| | | return nullptr; |
| | | } |
| | | if (!pTarSlot->isEnable() || pTarSlot->isLock() || pTarSlot->getContext() != nullptr) { |
| | | return nullptr; |
| | | } |
| | | |
| | | CRobotTask* pTask = new CRobotTask(); |
| | | pTask->setContext(pSrcSlot->getContext()); |
| | | pTask->setEFEM((CEFEM*)getEquipment(EQ_ID_EFEM)); |
| | | taskSeqNo = pTask->setRobotTransferParam(taskSeqNo, 1, pSrcSlot->getPosition(), |
| | | pTarSlot->getPosition(), pSrcSlot->getNo(), pTarSlot->getNo()); |
| | | |
| | | return pTask; |
| | | } |
| | | |
| | | CRobotTask* CMaster::createTransferTask_continuous_transfer(CEquipment* pSrcEq, int nSrcSlot, |
| | | CEquipment* pTarEq, int nTarSlot, int armNo/* = 1*/) |
| | | { |
| | |
| | | pPort->localSetCessetteType(type); |
| | | } |
| | | |
| | | void CMaster::applySchedulingModePortMapping() |
| | | { |
| | | // 生产模式:固定 Port1/Port3 为 G1,Port2/Port4 为 G2(G4 未定义,按 G2 处理) |
| | | if (m_schedulingMode != SchedulingMode::Production) { |
| | | return; |
| | | } |
| | | |
| | | setPortCassetteType(0, SERVO::CassetteType::G1); |
| | | setPortCassetteType(1, SERVO::CassetteType::G2); |
| | | setPortCassetteType(2, SERVO::CassetteType::G1); |
| | | setPortCassetteType(3, SERVO::CassetteType::G2); |
| | | } |
| | | |
| | | void CMaster::setPortEnable(unsigned int index, BOOL bEnable) |
| | | { |
| | | ASSERT(index < 4); |
| | |
| | | |
| | | static int pid[] = { EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4}; |
| | | CLoadPort* pPort = (CLoadPort*)getEquipment(pid[port]); |
| | | pPort->sendCassetteCtrlCmd(CCC_PROCESS_START, nullptr, 0, 0, 0, nullptr, nullptr); |
| | | if (pPort == nullptr) return -1; |
| | | |
| | | short jobExistence[12] = { 0 }; |
| | | short slotProcess = 0; |
| | | short jobCount = 0; // 0 = Process All Glass |
| | | bool anyScheduled = false; |
| | | |
| | | // Prefer hardware scan map for job existence (first 16 slots). |
| | | const short scanMap = pPort->getScanCassetteMap(); |
| | | if (scanMap != 0) { |
| | | jobExistence[0] = scanMap; |
| | | } |
| | | |
| | | const int maxSlots = 12 * 16; |
| | | const int totalSlots = (SLOT_MAX < maxSlots) ? SLOT_MAX : maxSlots; |
| | | for (int slot = 1; slot <= totalSlots; ++slot) { |
| | | CGlass* pGlass = pPort->getGlassFromSlot(slot); |
| | | if (pGlass == nullptr) continue; |
| | | |
| | | const int wordIndex = (slot - 1) / 16; |
| | | const int bitIndex = (slot - 1) % 16; |
| | | jobExistence[wordIndex] = (short)(jobExistence[wordIndex] | (1 << bitIndex)); |
| | | |
| | | if (slot <= 16 && pGlass->isScheduledForProcessing()) { |
| | | slotProcess = (short)(slotProcess | (1 << bitIndex)); |
| | | anyScheduled = true; |
| | | } |
| | | } |
| | | |
| | | if (!anyScheduled) { |
| | | slotProcess = jobExistence[0]; |
| | | } |
| | | |
| | | bool hasExistence = false; |
| | | for (short w : jobExistence) { |
| | | if (w != 0) { hasExistence = true; break; } |
| | | } |
| | | const int portStatus = pPort->getPortStatus(); |
| | | if (!hasExistence) { |
| | | LOGE("ProcessStart blocked (ProceedWithCarrier): no JobExistence map (port=%u, portStatus=%d, scanMap=%d, cassetteId=%s).", |
| | | port + 1, portStatus, scanMap, pPort->getCassetteId().c_str()); |
| | | return -2; |
| | | } |
| | | if (portStatus != PORT_INUSE) { |
| | | LOGW("ProcessStart warning (ProceedWithCarrier): port status is %d (expected INUSE).", portStatus); |
| | | } |
| | | LOGI("ProcessStart payload (ProceedWithCarrier): port=%u, cassetteId=%s, scanMap=%d, jobExistence0=%d, jobExistence1=%d, slotProcess=%d, anyScheduled=%d", |
| | | port + 1, pPort->getCassetteId().c_str(), scanMap, |
| | | (int)jobExistence[0], (int)jobExistence[1], (int)slotProcess, anyScheduled ? 1 : 0); |
| | | |
| | | pPort->sendCassetteCtrlCmd(CCC_PROCESS_START, jobExistence, 12, slotProcess, jobCount, nullptr, nullptr); |
| | | return 0; |
| | | } |
| | | |
| | |
| | | |
| | | this->saveState(); |
| | | if (m_listener.onControlJobChanged) { |
| | | m_listener.onControlJobChanged(this); |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | return (int)m_processJobs.size(); |
| | |
| | | m_pControlJob->setPJs(temps); |
| | | this->saveState(); |
| | | if (m_listener.onControlJobChanged) { |
| | | m_listener.onControlJobChanged(this); |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | bool CMaster::ceidDefined(uint32_t ceid) const |
| | | { |
| | | return true; |
| | | if (m_allowedCeids.empty()) return true; // backward compatible: treat as all allowed when not configured |
| | | return m_allowedCeids.find(ceid) != m_allowedCeids.end(); |
| | | } |
| | | |
| | | bool CMaster::raiseSoftAlarm(int alarmId, |
| | | const std::string& desc, |
| | | int level/* = -1*/, |
| | | int deviceId/* = 0*/, |
| | | int unitId/* = 0*/, |
| | | const char* deviceName/* = "Software"*/, |
| | | const char* unitName/* = "App"*/) |
| | | { |
| | | AlarmManager& alarmManager = AlarmManager::getInstance(); |
| | | const AlarmInfo* info = alarmManager.getAlarmInfoByID(alarmId); |
| | | |
| | | int severity = level; |
| | | if (severity < 0 && info != nullptr) { |
| | | severity = info->nAlarmLevel; |
| | | } |
| | | if (severity < 0) severity = 0; // fallback |
| | | |
| | | std::string descText = desc; |
| | | if (descText.empty() && info != nullptr) { |
| | | descText = !info->strDescription.empty() ? info->strDescription : info->strAlarmText; |
| | | } |
| | | if (descText.empty()) { |
| | | descText = CToolUnits::formatString("Alarm %d", alarmId); |
| | | } |
| | | |
| | | AlarmData alarmData; |
| | | alarmData.nId = alarmId; |
| | | alarmData.nSeverityLevel = severity; |
| | | alarmData.nDeviceId = deviceId; |
| | | alarmData.nUnitId = unitId; |
| | | alarmData.strDeviceName = deviceName; |
| | | alarmData.strUnitName = unitName; |
| | | alarmData.strStartTime = CToolUnits::timeToString2(CToolUnits::getTimestamp()); |
| | | alarmData.strEndTime = ""; |
| | | alarmData.strDescription = descText; |
| | | |
| | | int nAlarmEventId = 0; |
| | | return alarmManager.addAlarm(alarmData, nAlarmEventId); |
| | | } |
| | | |
| | | void CMaster::handleCollectionEvent(uint32_t ceid) |
| | | { |
| | | // 遍历当前 PJ,命中 pauseEvents 时可在此扩展暂停动作 |
| | | bool pausedAny = false; |
| | | for (auto pj : m_processJobs) { |
| | | if (pj == nullptr) continue; |
| | | const auto& pauseList = pj->pauseEvents(); |
| | | if (std::find(pauseList.begin(), pauseList.end(), ceid) != pauseList.end()) { |
| | | LOGW("<Master>PauseEvent hit: CEID=%u, PJ=%s, state=%d", ceid, pj->id().c_str(), (int)pj->state()); |
| | | if (pj->pause()) { |
| | | LOGI("<Master>PJ paused by CEID=%u", ceid); |
| | | pausedAny = true; |
| | | } |
| | | if (m_pControlJob != nullptr && m_pControlJob->state() == CJState::Executing) { |
| | | if (m_pControlJob->pause()) { |
| | | LOGI("<Master>ControlJob paused by CEID=%u", ceid); |
| | | pausedAny = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (pausedAny && m_listener.onControlJobChanged) { |
| | | // 通知应用层刷新 UI/按钮状态 |
| | | notifyControlJobChanged(); |
| | | } |
| | | if (pausedAny && !m_bPauseAlarmRaised) { |
| | | std::string desc = CToolUnits::formatString("<PauseEvent CEID=%u>", ceid); |
| | | bool raised = false; |
| | | if (m_pModelCtx != nullptr) { |
| | | raised = m_pModelCtx->raiseSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, desc); |
| | | } |
| | | else { |
| | | raised = raiseSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, desc); |
| | | } |
| | | if (raised) { |
| | | LOGI("<Master>PauseEvent soft alarm raised, CEID=%u", ceid); |
| | | m_bPauseAlarmRaised = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | void CMaster::setAllowedCeids(const std::vector<unsigned int>& ceids) |
| | | { |
| | | m_allowedCeids.clear(); |
| | | m_allowedCeids.reserve(ceids.size()); |
| | | for (auto id : ceids) { |
| | | m_allowedCeids.insert(id); |
| | | } |
| | | } |
| | | |
| | | bool CMaster::saveState() const |
| | |
| | | |
| | | saveState(); |
| | | if (m_listener.onControlJobChanged) { |
| | | m_listener.onControlJobChanged(this); |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | return true; |
| | |
| | | item->abort(description); |
| | | } |
| | | m_pControlJob->abort(description); |
| | | |
| | | // 先上报一次状态变化(便于 PrJobAbort 触发) |
| | | if (m_listener.onControlJobChanged) { |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | |
| | | // 释放Job相关 |
| | |
| | | |
| | | saveState(); |
| | | if (m_listener.onControlJobChanged) { |
| | | m_listener.onControlJobChanged(this); |
| | | notifyControlJobChanged(); |
| | | } |
| | | |
| | | return true; |
| | |
| | | if (pSlot == nullptr) return false; |
| | | |
| | | CGlass* pGlass = (CGlass*)pSlot->getContext(); |
| | | if (pGlass == nullptr) return false; |
| | | |
| | | // Buffer 上限为 1:新搬出时丢弃旧的 |
| | | if (!m_bufGlass.empty()) { |
| | | for (auto* oldGlass : m_bufGlass) { |
| | | if (oldGlass != nullptr) oldGlass->release(); |
| | | } |
| | | m_bufGlass.clear(); |
| | | } |
| | | m_bufGlass.push_back(pGlass); |
| | | pGlass->addRef(); |
| | | pSlot->setContext(nullptr); |
| | |
| | | auto& dataTypes = CServoUtilsTool::getEqDataTypes(); |
| | | auto& bonderTypes = dataTypes[MID_Bonder1]; |
| | | for (size_t i = 0; i < bonderTypes.size(); ++i) { |
| | | m_pCollector->buffersSetChannelName(MID_Bonder1, i + 1, bonderTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_Bonder2, i + 1, bonderTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_Bonder1, (UINT)i + 1, bonderTypes[(UINT)i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_Bonder2, (UINT)i + 1, bonderTypes[(UINT)i].c_str()); |
| | | } |
| | | |
| | | auto& vacuumbakeTypes = dataTypes[MID_VacuumBakeA]; |
| | | for (size_t i = 0; i < vacuumbakeTypes.size(); ++i) { |
| | | m_pCollector->buffersSetChannelName(MID_VacuumBakeA, i + 1, vacuumbakeTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_VacuumBakeB, i + 1, vacuumbakeTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_VacuumBakeA, (UINT)i + 1, vacuumbakeTypes[(UINT)i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_VacuumBakeB, (UINT)i + 1, vacuumbakeTypes[(UINT)i].c_str()); |
| | | } |
| | | |
| | | auto& coolingTypes = dataTypes[MID_BakeCoolingA]; |
| | |
| | | m_pCollector->buffersSetChannelName(MID_BakeCoolingA, i + 1, coolingTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(MID_BakeCoolingB, i + 1, coolingTypes[i].c_str()); |
| | | } |
| | | |
| | | if (m_curveMode == CurveMode::EmptyChamber) { |
| | | const uint32_t mids[] = { |
| | | MID_Bonder1, MID_Bonder2, |
| | | MID_VacuumBakeA, MID_VacuumBakeB, |
| | | MID_BakeCoolingA, MID_BakeCoolingB |
| | | }; |
| | | for (uint32_t mid : mids) { |
| | | m_pCollector->batchStart(mid, "EMPTY_CHAMBER", 10 * 60 * 1000ULL); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |