chenluhua1980
2026-01-08 3e91a18f75a75fbe8f646d73e4e68ba107b6750b
SourceCode/Bond/Servo/CMaster.cpp
@@ -222,6 +222,9 @@
      // 读缓存数据
      readCache();
      loadState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      // 定时器
@@ -1553,6 +1556,10 @@
               m_pCollector->batchStop(SlotToMid(eqid, slotNo));
            }
         }
         if (m_listener.onProcessStateChanged != nullptr) {
            m_listener.onProcessStateChanged(this, (CEquipment*)pEquipment, slotNo, state);
         }
      };
      listener.onMapMismatch = [&](void* pEquipment, short scanMap, short downMap) {
         LOGE("<Master-%s>Port InUse, map(%d!=%d)不一致,请检查。",
@@ -2031,6 +2038,172 @@
         }
      }
      // 模拟测试(无机器联机时用于联调 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) {
               if (m_listener.onProcessStateChanged != nullptr && pEq != nullptr) {
                  m_listener.onProcessStateChanged(this, pEq, 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;
            }
         }
      }
      // 模拟测试
@@ -2641,6 +2814,9 @@
      this->saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return (int)m_processJobs.size();
   }
@@ -2698,6 +2874,9 @@
      }
      m_pControlJob->setPJs(temps);
      this->saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return 0;
@@ -3040,6 +3219,9 @@
      saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return true;
   }
@@ -3074,6 +3256,9 @@
      saveState();
      if (m_listener.onControlJobChanged) {
         m_listener.onControlJobChanged(this);
      }
      return true;
   }