chenluhua1980
2026-01-10 9198ac12e4e2ff64a2cf65c32d576f02d54c346a
SourceCode/Bond/Servo/CMaster.cpp
@@ -222,6 +222,9 @@
      // 读缓存数据
      readCache();
      loadState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      // 定时器
@@ -1483,6 +1486,7 @@
                     this->saveState();
                     LOGE("<Master>ProcessJob(%s)完成.",
                        pJob->id().c_str());
                     processJobFromInPorcessToComplete(pJob);
                     if (m_listener.onPjEnd != nullptr) {
                        m_listener.onPjEnd(this, pJob);
                     }
@@ -1536,7 +1540,7 @@
            unlock();
         }
      };
      listener.onProcessStateChanged = [&](void* pEquipment, int slotNo, PROCESS_STATE state) -> void {
      listener.onProcessStateChanged = [&](void* pEquipment, int slotNo, PROCESS_STATE prevState, PROCESS_STATE state) -> void {
         ASSERT(1 <= slotNo && slotNo <= 8);
         int eqid = ((CEquipment*)pEquipment)->getID();
         CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(slotNo);
@@ -1551,6 +1555,15 @@
            if (pGlass != nullptr) {
               m_pCollector->batchStop(SlotToMid(eqid, slotNo));
            }
         }
         if (m_listener.onProcessStateChanged != nullptr) {
            m_listener.onProcessStateChanged(this, (CEquipment*)pEquipment, slotNo, prevState, state);
         }
      };
      listener.onProcessDataReport = [&](void* pEquipment, const std::vector<CParam>& params) {
         if (m_listener.onProcessDataReport != nullptr) {
            m_listener.onProcessDataReport(this, (CEquipment*)pEquipment, params);
         }
      };
      listener.onMapMismatch = [&](void* pEquipment, short scanMap, short downMap) {
@@ -1773,6 +1786,10 @@
            strOut.append(szBuffer);
         }
         LOGD("<CMaster-%s>SVDataReport:%s", ((CEquipment*)pEquipment)->getName().c_str(), strOut.c_str());
         if (m_listener.onSVDataReport != nullptr) {
            m_listener.onSVDataReport(this, (CEquipment*)pEquipment, params);
         }
      };
      listener.onPanelDataReport = [&](void* pEquipment, void* pContext) {
         LOGD("<CMaster-%s>onPanelDataReport", ((CEquipment*)pEquipment)->getName().c_str());
@@ -1792,6 +1809,11 @@
            }
         }
      };
      listener.onReceivedJob = [&](void* pEquipment, int port, CJobDataS* pJobDataS) {
         if (m_listener.onJobReceived != nullptr) {
            m_listener.onJobReceived(this, (CEquipment*)pEquipment, port, pJobDataS);
         }
      };
      pEquipment->setListener(listener);
      pEquipment->setCcLink(&m_cclink);
@@ -2030,6 +2052,173 @@
         }
      }
      // 模拟测试(无机器联机时用于联调 EAP)
      // 读取 test.ini(当前目录或 exe 同目录)
      {
         struct SimCfg {
            bool enabled{ false };
            DWORD intervalMs{ 5000 };
            int step{ 0 };
         };
         auto loadCfg = [&]() -> SimCfg {
            SimCfg cfg;
            // Try INI: current dir, then exe dir
            char iniPath[MAX_PATH] = { 0 };
            strcpy_s(iniPath, "test.ini");
            auto readIni = [&](const char* path) -> bool {
               const UINT en = GetPrivateProfileIntA("SimEap", "Enabled", 0, path);
               if (en == 0) return false; // treat as missing/disabled
               cfg.enabled = (en != 0);
               cfg.intervalMs = (DWORD)GetPrivateProfileIntA("SimEap", "IntervalMs", 5000, path);
               cfg.intervalMs = max(500u, cfg.intervalMs);
               cfg.step = (int)GetPrivateProfileIntA("SimEap", "Step", 0, path);
               return true;
            };
            if (!readIni(iniPath)) {
               char exePath[MAX_PATH] = { 0 };
               GetModuleFileNameA(NULL, exePath, MAX_PATH);
               char* lastSlash = strrchr(exePath, '\\');
               if (lastSlash != nullptr) {
                  *(lastSlash + 1) = '\0';
                  strcat_s(exePath, "test.ini");
                  readIni(exePath);
               }
            }
            return cfg;
         };
         const SimCfg cfg = loadCfg();
         if (cfg.enabled) {
            static DWORD lastTick = 0;
            static int lastExecutedStep = -1;
            static bool inited = false;
            static SERVO::CGlass simGlass;
            static SERVO::CVcrEventReport simVcr;
            static SERVO::CProcessJob simPj("PJ1001");
            static SERVO::CControlJob simCj("CJ5007");
            if (!inited) {
               inited = true;
               simGlass.setID("SIM_PANEL_001");
               simVcr.getGlassId() = "SIM_PANEL_001";
            }
            DWORD now = GetTickCount();
            if (lastTick == 0) lastTick = now;
            if ((now - lastTick) < cfg.intervalMs) {
               return;
            }
            lastTick = now;
            // 单步触发:每个 Step 只执行一次;你手动修改 ini 的 Step 值后会再次触发
            const int step = cfg.step;
            if (step <= 0 || step == lastExecutedStep) {
               return;
            }
            lastExecutedStep = step;
            // 取一个 LoadPort 作为模拟目标
            SERVO::CLoadPort* pLpEq = (SERVO::CLoadPort*)getEquipment(EQ_ID_LOADPORT1);
            auto fireLoadPortStatus = [&](short status) {
               pLpEq->simulateSetCassetteId("Test-Cassette-001");
               if (m_listener.onLoadPortStatusChanged != nullptr && pLpEq != nullptr) {
                  m_listener.onLoadPortStatusChanged(this, pLpEq, status, 0);
               }
            };
            auto fireProcessState = [&](SERVO::CEquipment* pEq, int slotNo, SERVO::PROCESS_STATE st) {
               // Drive equipment state so listeners receive prev/current states consistently.
               if (pEq != nullptr) {
                  pEq->fireSetProcessState(slotNo, st);
               }
            };
            LOGI("<Master>SIM_EAP single-step=%d", step);
            switch (step) {
               // ===== 业务流程步骤(1~23)=====
            case 1: // E87_06 Material Arrived(TransferBlock) -> Port Blocked
               fireLoadPortStatus(PORT_BLOCKED);
               break;
            case 2: // E87_03 CarrierID Readed -> Port InUse
               fireLoadPortStatus(PORT_INUSE);
               break;
            case 3: // S1F3 Query CJ Space (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step3: wait host S1F3");
               break;
            case 4: // S16F21 Query PJ Space (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step4: wait host S16F21");
               break;
            case 5: // S7F19 Query PPID List (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step5: wait host S7F19");
               break;
            case 6: // S3F17 ProceedWithCarrier (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step6: wait host S3F17 ProceedWithCarrier");
               break;
            case 7: // E87_14 Check SlotMap (设备上报/进入 WFH) - 由 PORT_INUSE 内部触发
               fireLoadPortStatus(PORT_INUSE);
               break;
            case 8: // S3F17 ProceedWithSlotMap (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step8: wait host S3F17 ProceedWithSlotMap");
               break;
            case 9: // SlotMap Verify OK (本项目在收到 ProceedWithSlotMap 后上报) - wait host
               LOGI("<Master>SIM_EAP step9: wait host ProceedWithSlotMap to trigger VerifyOK");
               break;
            case 10: // Create PJ (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step10: wait host S16F15 CreateMultiPJ");
               break;
            case 11: // PJ Queued(本项目在创建 PJ 后上报) - wait host
               LOGI("<Master>SIM_EAP step11: wait host CreateMultiPJ to trigger PJ_Queued");
               break;
            case 12: // Create CJ (Host->EQ) - wait host
               LOGI("<Master>SIM_EAP step12: wait host S14F9 CreateCJ");
               break;
            case 13: // CJ Start
               if (m_listener.onCjStart != nullptr) m_listener.onCjStart(this, &simCj);
               break;
            case 14: // PJ Start
               if (m_listener.onPjStart != nullptr) m_listener.onPjStart(this, &simPj);
               break;
            case 15: // OCR
               if (m_listener.onEqVcrEventReport != nullptr && pLpEq != nullptr) {
                  m_listener.onEqVcrEventReport(this, pLpEq, &simVcr);
               }
               break;
            case 16: // Panel Start
               if (m_listener.onPanelStart != nullptr) m_listener.onPanelStart(this, &simGlass);
               // 同时触发一次子机台开始(示例:Bonder1, slot 1)
               fireProcessState(getEquipment(EQ_ID_Bonder1), 1, SERVO::PROCESS_STATE::Processing);
               break;
            case 17: // Panel End
               // 同时触发一次子机台结束(示例:Bonder1, slot 1)
               fireProcessState(getEquipment(EQ_ID_Bonder1), 1, SERVO::PROCESS_STATE::Complete);
               if (m_listener.onPanelEnd != nullptr) m_listener.onPanelEnd(this, &simGlass);
               break;
            case 18: // PJ End
               if (m_listener.onPjEnd != nullptr) m_listener.onPjEnd(this, &simPj);
               break;
            case 19: // CJ End
               if (m_listener.onCjEnd != nullptr) m_listener.onCjEnd(this, &simCj);
               break;
            case 20: // Ready to Release (Port Unload Ready; with prev INUSE will also trigger ReadyToRelease)
               fireLoadPortStatus(PORT_UNLOAD_READY);
               break;
            case 21: // CarrierRelease (Host->EQ) - optional / wait host
               LOGI("<Master>SIM_EAP step21: wait host S3F17 CarrierRelease");
               break;
            case 22: // Ready to Unload
               fireLoadPortStatus(PORT_UNLOAD_READY);
               break;
            case 23: // Material Removed (and ReadyToLoad)
               fireLoadPortStatus(PORT_LOAD_READY);
               fireLoadPortStatus(PORT_EMPTY); // will also raise LoadPortNotAssoc via Model
               break;
            default:
               break;
            }
         }
      }
      // 模拟测试
@@ -2600,15 +2789,31 @@
      }
      m_processJobs = temp;
      // 重置各端口 DownloadMap(Host/本地勾选的期望加工槽位)
      for (int i = 0; i < 4; i++) {
         auto* pPort = (CLoadPort*)getEquipment(EQ_ID_LOADPORT1 + i);
         if (pPort != nullptr) {
            pPort->setDownloadCassetteMap(0);
         }
      }
      // 更新context
      std::vector<uint8_t> newSlots;
      std::vector<void*> newContexts;
      for (auto pj : m_processJobs) {
         for (auto& c : pj->carriers()) {
            auto pPort = getPortWithCarrierId(c.carrierId);
            if (pPort == nullptr) continue;
            short downloadMap = 0;
            for (auto s : c.slots) {
               if (s >= 1 && s <= 8) {
                  downloadMap |= (short)(1 << (s - 1));
               }
            }
            pPort->setDownloadCassetteMap((short)(pPort->getDownloadCassetteMap() | downloadMap));
            std::vector<uint8_t> newSlots;
            std::vector<void*> newContexts;
            for (auto s : c.slots) {
               auto pGlass = pPort->getGlassFromSlot(s);
               if (pGlass == nullptr) continue;
@@ -2624,6 +2829,9 @@
      this->saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return (int)m_processJobs.size();
   }
@@ -2681,6 +2889,9 @@
      }
      m_pControlJob->setPJs(temps);
      this->saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return 0;
@@ -3023,6 +3234,9 @@
      saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return true;
   }
@@ -3057,6 +3271,9 @@
      saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return true;
   }