LAPTOP-SNT8I5JK\Boounion
2025-08-14 a2209cc432cb9c80779d83e51ef090f782e8404a
1.Create PJ功能实现;
已添加6个文件
已修改17个文件
1416 ■■■■■ 文件已修改
Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CPJsDlg.cpp 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CPJsDlg.h 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulator.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulator.vcxproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulator.vcxproj.filters 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/ProcessJob.cpp 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/ProcessJob.h 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/Resource.h 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.cpp 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.h 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Document/Panel Bonder°ËÁãÁªºÏ SecsTest CheckList_v3.0.xlsx
Binary files differ
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
@@ -3,6 +3,8 @@
#include "Log.h"
static unsigned int DATAID = 1;
CHsmsActive::CHsmsActive()
{
    m_listener = {};
@@ -343,6 +345,42 @@
    return hsmsCarrierActionRequest(DATAID, "CarrierRelease", pszCarrierId, PTN);
}
int CHsmsActive::hsmsPRJobMultiCreate(std::vector<SERVO::CProcessJob*>& pjs)
{
    IMessage* pMessage = nullptr;
    int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 16 | REPLY, 15, ++m_nSystemByte);
    char szMF[32] = {14};
    pMessage->getBody()->addU4Item(++DATAID, "DATAID");
    auto itemPjs = pMessage->getBody()->addItem();
    for (auto pj : pjs) {
        auto itemPj = itemPjs->addItem();
        itemPj->addItem(pj->id().c_str(), "PRJOBID");
        itemPj->addBinaryItem(szMF, 1, "MF");
        auto itemCarriers = itemPj->addItem();
        for (auto c : pj->carriers()) {
            auto itemCarrier = itemCarriers->addItem();
            itemCarrier->addItem(c.carrierId.c_str(), "CARRIERID");
            auto itemSlots = itemCarrier->addItem();
            for (auto s : c.slots) {
                itemSlots->addU1Item(s, "SLOTID");
            }
        }
        auto recipeItems = itemPj->addItem();
        recipeItems->addU1Item(1, "PRRECIPEMETHOD");
        recipeItems->addItem(pj->recipeSpec().c_str(), "RCPSPEC");
        recipeItems->addItem();
        itemPj->addBoolItem(false, "PRPROCESSSTART");
        itemPj->addItem();
    }
    m_pActive->sendMessage(pMessage);
    HSMS_Destroy1Message(pMessage);
    return 0;
}
int CHsmsActive::replyAck0(IMessage* pMessage)
{
    return 0;
SourceCode/Bond/EAPSimulator/CHsmsActive.h
@@ -4,6 +4,7 @@
#include <map>
#include <set>
#include "CCollectionEvent.h"
#include "ProcessJob.h"
#define SVID_CJobSpace                5001
@@ -84,6 +85,8 @@
        const char* pszCarrierId,
        unsigned char PTN);
    // S16F15
    int hsmsPRJobMultiCreate(std::vector<SERVO::CProcessJob*>& pjs);
    // é€šè¿‡çš„reply函数
    void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
SourceCode/Bond/EAPSimulator/CPJsDlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,161 @@
// CPJsDlg.cpp: å®žçŽ°æ–‡ä»¶
//
#include "pch.h"
#include "EAPSimulator.h"
#include "CPJsDlg.h"
#include "afxdialogex.h"
// CPJsDlg å¯¹è¯æ¡†
IMPLEMENT_DYNAMIC(CPJsDlg, CDialogEx)
CPJsDlg::CPJsDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_PJS, pParent)
{
}
CPJsDlg::~CPJsDlg()
{
    for (auto item : m_pjs) {
        delete item;
    }
    m_pjs.clear();
}
void CPJsDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CPJsDlg, CDialogEx)
    ON_BN_CLICKED(IDC_BUTTON_ADD, &CPJsDlg::OnBnClickedButtonAdd)
    ON_BN_CLICKED(IDC_BUTTON_DELETE, &CPJsDlg::OnBnClickedButtonDelete)
    ON_BN_CLICKED(IDC_BUTTON_SEND, &CPJsDlg::OnBnClickedButtonSend)
END_MESSAGE_MAP()
// CPJsDlg æ¶ˆæ¯å¤„理程序
void CPJsDlg::OnBnClickedButtonAdd()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
}
void CPJsDlg::OnBnClickedButtonDelete()
{
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST1);
    int nSel = pListCtrl->GetNextItem(-1, LVNI_SELECTED);
    if (nSel != -1) {
        SERVO::CProcessJob* pj = (SERVO::CProcessJob*)pListCtrl->GetItemData(nSel);
        for (auto iter = m_pjs.begin(); iter != m_pjs.end(); ++iter) {
            if (*iter == pj) {
                delete (*iter);
                m_pjs.erase(iter);
                break;
            }
        }
    }
    pListCtrl->DeleteItem(nSel);
}
void CPJsDlg::OnBnClickedButtonSend()
{
    theApp.m_model.m_pHsmsActive->hsmsPRJobMultiCreate(m_pjs);
}
BOOL CPJsDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // æŠ¥è¡¨æŽ§ä»¶
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST1);
    DWORD dwStyle = pListCtrl->GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    pListCtrl->SetExtendedStyle(dwStyle);
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(pListCtrl->GetSafeHwnd(), imageList, LVSIL_SMALL);
    pListCtrl->InsertColumn(0, _T(""), LVCFMT_RIGHT, 0);
    pListCtrl->InsertColumn(1, _T("PJ ID"), LVCFMT_LEFT, 100);
    pListCtrl->InsertColumn(2, _T("Carrier1 & Slots"), LVCFMT_LEFT, 180);
    pListCtrl->InsertColumn(3, _T("Carrier2 & Slots"), LVCFMT_LEFT, 180);
    pListCtrl->InsertColumn(4, _T("Carrier3 & Slots"), LVCFMT_LEFT, 180);
    pListCtrl->InsertColumn(5, _T("Carrier4 & Slots"), LVCFMT_LEFT, 180);
    pListCtrl->InsertColumn(6, _T("PPID"), LVCFMT_LEFT, 180);
    // åˆ›å»ºç”¨äºŽæµ‹è¯•çš„PJ
    {
        SERVO::CProcessJob* pj = new SERVO::CProcessJob("PJ0001");
        std::vector<uint8_t> slots1{ 1, 2, 3 };
        pj->addCarrier("CID1001", slots1);
        pj->setRecipe(SERVO::RecipeMethod::NoTuning, "P1001");
        m_pjs.push_back(pj);
    }
    {
        SERVO::CProcessJob* pj = new SERVO::CProcessJob("PJ0002");
        std::vector<uint8_t> slots1{ 1, 3 };
        pj->addCarrier("CID1002", slots1);
        std::vector<uint8_t> slots2{ 1};
        pj->addCarrier("CID1003", slots2);
        pj->setRecipe(SERVO::RecipeMethod::NoTuning, "R002");
        m_pjs.push_back(pj);
    }
    {
        SERVO::CProcessJob* pj = new SERVO::CProcessJob("PJ0003");
        std::vector<uint8_t> slots1{ 1, 2, 3, 5 };
        pj->addCarrier("CID1004", slots1);
        pj->setRecipe(SERVO::RecipeMethod::NoTuning, "P1001");
        m_pjs.push_back(pj);
    }
    // æ˜¾ç¤ºåˆ°æŠ¥è¡¨ä¸­
    for (auto item : m_pjs) {
        AddPjToListCtrl(item);
    }
    return TRUE;  // return TRUE unless you set the focus to a control
                  // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
}
void CPJsDlg::AddPjToListCtrl(SERVO::CProcessJob* pj)
{
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST1);
    pListCtrl->InsertItem(0, _T(""));
    pListCtrl->SetItemData(0, (DWORD_PTR)pj);
    pListCtrl->SetItemText(0, 1, pj->id().c_str());
    pListCtrl->SetItemText(0, 6, pj->recipeSpec().c_str());
    auto carries = pj->carriers();
    for (int i = 0; i < min(4, carries.size()); i++) {
        pListCtrl->SetItemText(0, 2 + i, GetFormatString(carries[i]));        ;
    }
}
CString CPJsDlg::GetFormatString(SERVO::CarrierSlotInfo& csi)
{
    CString strRet;
    strRet.Append(csi.carrierId.c_str());
    strRet.Append("<");
    int size = min(8, csi.slots.size());
    for (int i = 0; i < size; i++) {
        strRet.Append(std::to_string(csi.slots[i]).c_str());
        if (i != size - 1) {
            strRet.Append(",");
        }
    }
    strRet.Append(">");
    return strRet;
}
SourceCode/Bond/EAPSimulator/CPJsDlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
#pragma once
#include "ProcessJob.h"
#include <vector>
// CPJsDlg å¯¹è¯æ¡†
class CPJsDlg : public CDialogEx
{
    DECLARE_DYNAMIC(CPJsDlg)
public:
    CPJsDlg(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CPJsDlg();
    void AddPjToListCtrl(SERVO::CProcessJob* pj);
    CString GetFormatString(SERVO::CarrierSlotInfo& csi);
private:
    std::vector<SERVO::CProcessJob*> m_pjs;
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_PJS };
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV æ”¯æŒ
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedButtonAdd();
    afx_msg void OnBnClickedButtonDelete();
    afx_msg void OnBnClickedButtonSend();
    virtual BOOL OnInitDialog();
};
SourceCode/Bond/EAPSimulator/EAPSimulator.rc
Binary files differ
SourceCode/Bond/EAPSimulator/EAPSimulator.vcxproj
@@ -93,6 +93,7 @@
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
      <LanguageStandard>stdcpp17</LanguageStandard>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
@@ -193,6 +194,7 @@
    <ClInclude Include="CModel.h" />
    <ClInclude Include="Common.h" />
    <ClInclude Include="Context.h" />
    <ClInclude Include="CPJsDlg.h" />
    <ClInclude Include="CReport.h" />
    <ClInclude Include="CTerminalDisplayDlg.h" />
    <ClInclude Include="CVariable.h" />
@@ -202,6 +204,7 @@
    <ClInclude Include="Log.h" />
    <ClInclude Include="LogEdit.h" />
    <ClInclude Include="pch.h" />
    <ClInclude Include="ProcessJob.h" />
    <ClInclude Include="Resource.h" />
    <ClInclude Include="targetver.h" />
  </ItemGroup>
@@ -215,6 +218,7 @@
    <ClCompile Include="CLinkReportDlg.cpp" />
    <ClCompile Include="CModel.cpp" />
    <ClCompile Include="Context.cpp" />
    <ClCompile Include="CPJsDlg.cpp" />
    <ClCompile Include="CReport.cpp" />
    <ClCompile Include="CTerminalDisplayDlg.cpp" />
    <ClCompile Include="CVariable.cpp" />
@@ -228,6 +232,7 @@
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
    </ClCompile>
    <ClCompile Include="ProcessJob.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="EAPSimulator.rc" />
SourceCode/Bond/EAPSimulator/EAPSimulator.vcxproj.filters
@@ -78,6 +78,12 @@
    <ClInclude Include="CLinkReportDetailDlg.h">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="CPJsDlg.h">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="ProcessJob.h">
      <Filter>头文件</Filter>
    </ClInclude>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="EAPSimulator.cpp">
@@ -131,6 +137,12 @@
    <ClCompile Include="CLinkReportDetailDlg.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="CPJsDlg.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="ProcessJob.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="EAPSimulator.rc">
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
@@ -13,6 +13,7 @@
#include "CEDEventReportDlg.h"
#include "CDefineReportsDlg.h"
#include "CLinkReportDlg.h"
#include "CPJsDlg.h"
#ifdef _DEBUG
@@ -92,6 +93,7 @@
    ON_BN_CLICKED(IDC_BUTTON_CARRIER_RELEASE, &CEAPSimulatorDlg::OnBnClickedButtonCarrierRelease)
    ON_BN_CLICKED(IDC_BUTTON_QUERY_CJ_SPACE, &CEAPSimulatorDlg::OnBnClickedButtonQueryCjSpace)
    ON_BN_CLICKED(IDC_BUTTON_QUERY_PJ_SPACE, &CEAPSimulatorDlg::OnBnClickedButtonQueryPjSpace)
    ON_BN_CLICKED(IDC_BUTTON_CREATE_PJ, &CEAPSimulatorDlg::OnBnClickedButtonCreatePj)
END_MESSAGE_MAP()
@@ -283,6 +285,7 @@
    GetDlgItem(IDC_BUTTON_CARRIER_RELEASE)->EnableWindow(enabled);
    GetDlgItem(IDC_BUTTON_QUERY_CJ_SPACE)->EnableWindow(enabled);
    GetDlgItem(IDC_BUTTON_QUERY_PJ_SPACE)->EnableWindow(enabled);
    GetDlgItem(IDC_BUTTON_CREATE_PJ)->EnableWindow(enabled);
}
void CEAPSimulatorDlg::OnBnClickedButtonConnect()
@@ -405,3 +408,9 @@
{
    theApp.m_model.m_pHsmsActive->hsmsCarrierRelease(DATAID++, "CSX 52078", 2);
}
void CEAPSimulatorDlg::OnBnClickedButtonCreatePj()
{
    CPJsDlg dlg;
    dlg.DoModal();
}
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
@@ -61,4 +61,5 @@
    afx_msg void OnBnClickedButtonCarrierRelease();
    afx_msg void OnBnClickedButtonQueryCjSpace();
    afx_msg void OnBnClickedButtonQueryPjSpace();
    afx_msg void OnBnClickedButtonCreatePj();
};
SourceCode/Bond/EAPSimulator/ProcessJob.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,252 @@
#include "pch.h"
#include "ProcessJob.h"
#include <cctype>
namespace SERVO {
    static inline std::string trimCopy(std::string s) {
        auto notspace = [](int ch) { return !std::isspace(ch); };
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), notspace));
        s.erase(std::find_if(s.rbegin(), s.rend(), notspace).base(), s.end());
        return s;
    }
    CProcessJob::CProcessJob(std::string pjId)
        : m_pjId(trimCopy(pjId))
    {
        clampString(m_pjId, MAX_ID_LEN);
    }
    void CProcessJob::setParentCjId(std::string cjId) {
        m_parentCjId = trimCopy(cjId);
        clampString(m_parentCjId, MAX_ID_LEN);
    }
    void CProcessJob::setRecipe(RecipeMethod method, std::string spec) {
        m_recipeMethod = method;
        m_recipeSpec = trimCopy(spec);
        clampString(m_recipeSpec, MAX_ID_LEN);
    }
    void CProcessJob::addParam(std::string name, std::string value) {
        name = trimCopy(name);
        value = trimCopy(value);
        clampString(name, MAX_PARAM_K);
        clampString(value, MAX_PARAM_V);
        m_params.push_back({ std::move(name), std::move(value) });
    }
    void CProcessJob::setParams(std::vector<PJParam> params) {
        m_params.clear();
        m_params.reserve(params.size());
        for (auto& p : params) addParam(std::move(p.name), std::move(p.value));
    }
    void CProcessJob::addPauseEvent(uint32_t ceid) {
        if (ceid) m_pauseEvents.push_back(ceid);
        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
    }
    void CProcessJob::setPauseEvents(std::vector<uint32_t> ceids) {
        m_pauseEvents = std::move(ceids);
        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
    }
    std::vector<CProcessJob::ValidationIssue>
        CProcessJob::validate(const IResourceView& rv) const
    {
        std::vector<ValidationIssue> issues;
        // è®© add åŒæ—¶æ”¯æŒ const char* å’Œ std::string
        auto add = [&](uint32_t code, std::string msg) {
            issues.push_back({ code, std::move(msg) });
        };
        // â€”— åŸºæœ¬ / æ ‡è¯† â€”—
        if (m_pjId.empty())            add(1001, "PJID empty");
        if (!asciiPrintable(m_pjId))   add(1002, "PJID has non-printable chars");
        if (m_parentCjId.empty())      add(1010, "Parent CJID empty");
        // â€”— é…æ–¹ï¼ˆRCPSPEC / PPID)——
        if (m_recipeSpec.empty())      add(1020, "Recipe spec (PPID) empty");
        else if (!rv.recipeExists(m_recipeSpec)) {
            add(1021, "PPID not found: " + m_recipeSpec);
        }
        // â€”— é…æ–¹æ–¹æ³• vs å‚æ•° â€”— 1=NoTuning ç¦æ­¢å¸¦å‚æ•°ï¼›2=WithTuning å…è®¸/可选
        if (m_recipeMethod == RecipeMethod::NoTuning && !m_params.empty()) {
            add(1022, "Params not allowed when PRRECIPEMETHOD=1 (NoTuning)");
        }
        // â€”— ç‰©æ–™é€‰æ‹©æ ¡éªŒ â€”—(二选一:Carrier+Slots æˆ– MIDs;两者都不填则错误)
        const bool hasCarrierSlots = !m_carriers.empty();
        if (hasCarrierSlots) {
            // {L:n { CARRIERID {L:j SLOTID} }}
            for (const auto& cs : m_carriers) {
                if (cs.carrierId.empty()) {
                    add(1030, "CarrierID empty");
                    continue;
                }
                if (!rv.carrierPresent(cs.carrierId)) {
                    add(1031, "Carrier not present: " + cs.carrierId);
                }
                if (cs.slots.empty()) {
                    add(1032, "No slots specified for carrier: " + cs.carrierId);
                }
                for (auto s : cs.slots) {
                    if (s == 0) {
                        add(1033, "Slot 0 is invalid for carrier: " + cs.carrierId);
                        continue;
                    }
                    if (!rv.slotUsable(cs.carrierId, s)) {
                        add(1034, "Slot unusable: carrier=" + cs.carrierId + " slot=" + std::to_string(s));
                    }
                }
            }
        }
        else {
            add(1035, "No material selection provided (neither Carrier/Slots nor MIDs)");
        }
        // â€”— æš‚停事件(PRPAUSEEVENTID åˆ—表)——
        for (auto ceid : m_pauseEvents) {
            if (!rv.ceidDefined(ceid)) {
                add(1050, "Pause CEID unknown: " + std::to_string(ceid));
            }
        }
        return issues;
    }
    // â€”— çŠ¶æ€æœº â€”—
    // è§„则可按你们协议微调
    bool CProcessJob::queue() {
        if (m_state != PJState::NoState) return false;
        markQueued();
        return true;
    }
    bool CProcessJob::enterSettingUp() {
        if (m_state != PJState::Queued) return false;
        m_state = PJState::SettingUp;
        return true;
    }
    bool CProcessJob::start() {
        if (m_state != PJState::Queued && m_state != PJState::SettingUp && m_state != PJState::Paused)
            return false;
        if (!m_tStart.has_value()) markStart();
        m_state = PJState::InProcess;
        return true;
    }
    bool CProcessJob::pause() {
        if (m_state != PJState::InProcess) return false;
        m_state = PJState::Paused;
        return true;
    }
    bool CProcessJob::resume() {
        if (m_state != PJState::Paused) return false;
        m_state = PJState::InProcess;
        return true;
    }
    bool CProcessJob::complete() {
        if (m_state != PJState::InProcess && m_state != PJState::Paused) return false;
        m_state = PJState::Completed;
        markEnd();
        return true;
    }
    bool CProcessJob::abort() {
        if (m_state == PJState::Completed || m_state == PJState::Aborted || m_state == PJState::Failed)
            return false;
        m_state = PJState::Aborted;
        markEnd();
        return true;
    }
    bool CProcessJob::fail(std::string reason) {
        m_failReason = trimCopy(reason);
        clampString(m_failReason, 128);
        m_state = PJState::Failed;
        markEnd();
        return true;
    }
    // â€”— æ—¶é—´æˆ³ & å·¥å…· â€”—
    void CProcessJob::markQueued() {
        m_state = PJState::Queued;
        m_tQueued = std::chrono::system_clock::now();
    }
    void CProcessJob::markStart() {
        m_tStart = std::chrono::system_clock::now();
    }
    void CProcessJob::markEnd() {
        m_tEnd = std::chrono::system_clock::now();
    }
    void CProcessJob::clampString(std::string& s, size_t maxLen) {
        if (s.size() > maxLen) s.resize(maxLen);
    }
    bool CProcessJob::asciiPrintable(const std::string& s) {
        return std::all_of(s.begin(), s.end(), [](unsigned char c) {
            return c >= 0x20 && c <= 0x7E;
            });
    }
    void CProcessJob::setCarriers(std::vector<CarrierSlotInfo> carriers)
    {
        // ç»Ÿä¸€é€šè¿‡ addCarrier åšè§„范化(去空白、截断、去重、合并同 carrier)
        m_carriers.clear();
        m_carriers.reserve(carriers.size());
        for (auto& cs : carriers) {
            addCarrier(std::move(cs.carrierId), std::move(cs.slots));
        }
    }
    void CProcessJob::addCarrier(std::string carrierId, std::vector<uint8_t> slots)
    {
        // 1) è§„范化 carrierId:去空白 + é•¿åº¦é™åˆ¶
        carrierId = trimCopy(std::move(carrierId));
        clampString(carrierId, MAX_ID_LEN);
        if (carrierId.empty()) {
            // ç©º ID ç›´æŽ¥å¿½ç•¥ï¼ˆä¹Ÿå¯ä»¥é€‰æ‹©æŠ›å¼‚常/记录日志,看你项目风格)
            return;
        }
        // 2) è§„范化 slots:去 0、排序、去重
        //    æ³¨ï¼šSLOTID æŒ‰ 1..N,0 è§†ä¸ºéžæ³•/占位
        slots.erase(std::remove(slots.begin(), slots.end(), 0), slots.end());
        std::sort(slots.begin(), slots.end());
        slots.erase(std::unique(slots.begin(), slots.end()), slots.end());
        if (slots.empty()) {
            // æ²¡æœ‰æœ‰æ•ˆå¡ä½å°±ä¸è¿½åŠ 
            return;
        }
        // 3) å¦‚果已存在同名载具,则合并 slot åˆ—表
        auto it = std::find_if(m_carriers.begin(), m_carriers.end(),
            [&](const CarrierSlotInfo& cs) { return cs.carrierId == carrierId; });
        if (it != m_carriers.end()) {
            // åˆå¹¶
            it->slots.insert(it->slots.end(), slots.begin(), slots.end());
            std::sort(it->slots.begin(), it->slots.end());
            it->slots.erase(std::unique(it->slots.begin(), it->slots.end()), it->slots.end());
        }
        else {
            // æ–°å¢ž
            CarrierSlotInfo cs;
            cs.carrierId = std::move(carrierId);
            cs.slots = std::move(slots);
            m_carriers.emplace_back(std::move(cs));
        }
    }
}
SourceCode/Bond/EAPSimulator/ProcessJob.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,198 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <cstdint>
#include <chrono>
#include <optional>
namespace SERVO {
    /// PJ ç”Ÿå‘½å‘¨æœŸï¼ˆè´´è¿‘ E40 å¸¸è§çŠ¶æ€ï¼‰
    enum class PJState : uint8_t {
        NoState = 0,
        Queued,
        SettingUp,
        InProcess,
        Paused,
        Aborting,
        Completed,
        Aborted,
        Failed
    };
    /// é…æ–¹æŒ‡å®šæ–¹å¼ï¼ˆå¯¹åº” S16F15 é‡Œ PRRECIPEMETHOD)
    enum class RecipeMethod : uint8_t {
        NoTuning = 1,   // 1 - recipe without variable tuning
        WithTuning = 2  // 2 - recipe with variable tuning
    };
    /// å¯åŠ¨ç­–ç•¥ï¼ˆå¯¹åº” S16F15 é‡Œ PRPROCESSSTART)
    enum class StartPolicy : uint8_t {
        Queued = 0,   // å»ºç«‹åŽæŽ’队
        AutoStart = 1 // æ¡ä»¶æ»¡è¶³åˆ™è‡ªåŠ¨å¯åŠ¨
    };
    /** é…æ–¹å‚数对(S16F15 ä¸­ RCPPARNM / RCPPARVAL) */
    struct PJParam {
        std::string name;   // RCPPARNM
        std::string value;  // RCPPARVAL
    };
    /**
    {L:2
        CARRIERID
        {L:j
            SLOTID
        }
    }
     */
    struct CarrierSlotInfo {
        std::string carrierId;              // CARRIERID
        std::vector<uint8_t> slots;        // SLOTID[]
    };
    /// ç®€å•资源视图接口:供 Validate() æŸ¥è¯¢ï¼ˆç”±è®¾å¤‡ç«¯å®žçŽ°è€…åœ¨å¤–éƒ¨æä¾›ï¼‰
    struct IResourceView {
        virtual ~IResourceView() = default;
        virtual bool recipeExists(const std::string& ppid) const = 0;
        virtual bool carrierPresent(const std::string& carrierId) const = 0;
        virtual bool slotUsable(const std::string& carrierId, uint16_t slot) const = 0;
        virtual bool ceidDefined(uint32_t ceid) const = 0;
        // ä½ ä¹Ÿå¯ä»¥æ‰©å±•:port状态、占用情况、CJ/PJ空间等
    };
    /// PJ ä¸»ç±»
    /**
     * ProcessJob â€”— ä¸Ž S16F15(PRJobMultiCreate)字段一一对应的承载类
     *
     * S16F15 ç»“构(核心节选):
     * {L:6
     *   PRJOBID                -> m_pjId
     *   MF                     -> m_mf
     *   {L:n { CARRIERID {L:j SLOTID} } }
     *   {L:3
     *     PRRECIPEMETHOD       -> m_recipeType
     *     RCPSPEC(PPID)      -> m_recipeSpec
     *     {L:m { RCPPARNM RCPPARVAL }}     -> m_params
     *   }
     *   PRPROCESSSTART         -> m_startPolicy
     *   {L:k PRPAUSEEVENTID}   -> m_pauseEvents
     * }
     */
    class CProcessJob {
    public:
        // â€”— æž„造 / åŸºæœ¬è®¾ç½® â€”—
        explicit CProcessJob(std::string pjId);
        const std::string& id() const noexcept { return m_pjId; }
        const std::string& parentCjId() const noexcept { return m_parentCjId; }
        PJState state() const noexcept { return m_state; }
        StartPolicy startPolicy() const noexcept { return m_startPolicy; }
        RecipeMethod recipeMethod() const noexcept { return m_recipeMethod; }
        const std::string& recipeSpec() const noexcept { return m_recipeSpec; } // PPID æˆ– Spec
        // ç»‘定父 CJ
        void setParentCjId(std::string cjId);
        // é…æ–¹
        void setRecipe(RecipeMethod method, std::string spec);
        // å¯åŠ¨ç­–ç•¥
        void setStartPolicy(StartPolicy sp) { m_startPolicy = sp; }
        // å‚æ•°
        void addParam(std::string name, std::string value);
        void setParams(std::vector<PJParam> params);
        // æš‚停事件
        void addPauseEvent(uint32_t ceid);
        void setPauseEvents(std::vector<uint32_t> ceids);
        // â€”— æ ¡éªŒ â€”—
        struct ValidationIssue {
            uint32_t code;      // è‡ªå®šä¹‰é”™è¯¯ç 
            std::string text;   // æ–‡æœ¬æè¿°
        };
        // è¿”回问题清单(空=通过)
        std::vector<ValidationIssue> validate(const IResourceView& rv) const;
        // â€”— çŠ¶æ€æœºï¼ˆå¸¦å®ˆå«ï¼‰â€”â€”
        bool queue();           // NoState -> Queued
        bool start();           // Queued/SettingUp -> InProcess
        bool enterSettingUp();  // Queued -> SettingUp
        bool pause();           // InProcess -> Paused
        bool resume();          // Paused -> InProcess
        bool complete();        // InProcess -> Completed
        bool abort();           // Any (未终态) -> Aborted
        bool fail(std::string reason); // ä»»æ„æ€ -> Failed(记录失败原因)
        // â€”— è®¿é—®å™¨ï¼ˆç”¨äºŽä¸ŠæŠ¥/查询)——
        const std::vector<PJParam>& params() const noexcept { return m_params; }
        const std::vector<uint32_t>& pauseEvents() const noexcept { return m_pauseEvents; }
        const std::string& failReason() const noexcept { return m_failReason; }
        // æ—¶é—´æˆ³ï¼ˆå¯ç”¨äºŽæŠ¥è¡¨/追溯)
        std::optional<std::chrono::system_clock::time_point> tQueued() const { return m_tQueued; }
        std::optional<std::chrono::system_clock::time_point> tStart()  const { return m_tStart; }
        std::optional<std::chrono::system_clock::time_point> tEnd()    const { return m_tEnd; }
        // é•¿åº¦é™åˆ¶å·¥å…·ï¼ˆå¯åœ¨é›†æˆæ—¶ç»Ÿä¸€ç­–略)
        static void clampString(std::string& s, size_t maxLen);
        static bool asciiPrintable(const std::string& s);
        // æ¸…空并整体设置
        void setCarriers(std::vector<CarrierSlotInfo> carriers);
        // è¿½åŠ ä¸€ä¸ªè½½å…·
        void addCarrier(std::string carrierId, std::vector<uint8_t> slots);
        // è®¿é—®å™¨
        const std::vector<CarrierSlotInfo>& carriers() const noexcept { return m_carriers; }
        // åˆ¤å®šæ˜¯å¦â€œæŒ‰è½½å…·/卡位”方式
        bool usesCarrierSlots() const noexcept { return !m_carriers.empty(); }
    private:
        // å†…部状态转移帮助
        void markQueued();
        void markStart();
        void markEnd();
    private:
        // æ ‡è¯†
        std::string m_pjId;
        std::string m_parentCjId;
        // é…æ–¹
        RecipeMethod m_recipeMethod{ RecipeMethod::NoTuning };
        std::string  m_recipeSpec; // PPID / Spec
        // ç‰©æ–™
        static constexpr uint8_t MATERIAL_FORMAT = 14; // substrate
        std::vector<CarrierSlotInfo> m_carriers;   // {L:n { CARRIERID {L:j SLOTID} }}
        // å‚æ•° / æš‚停事件
        std::vector<PJParam>    m_params;
        std::vector<uint32_t>   m_pauseEvents;
        // çŠ¶æ€ & è®°å½•
        StartPolicy m_startPolicy{ StartPolicy::Queued }; // 0=Queued, 1=AutoStart
        PJState m_state{ PJState::NoState };
        std::string m_failReason;
        // æ—¶é—´æˆ³
        std::optional<std::chrono::system_clock::time_point> m_tQueued;
        std::optional<std::chrono::system_clock::time_point> m_tStart;
        std::optional<std::chrono::system_clock::time_point> m_tEnd;
        // çº¦æŸï¼ˆå¯æŒ‰ä½ ä»¬åè®®è°ƒæ•´ï¼‰
        static constexpr size_t MAX_ID_LEN = 64;   // PJID/ CJID/ CarrierID/ MID/ PPID
        static constexpr size_t MAX_PARAM_K = 32;   // å‚数名
        static constexpr size_t MAX_PARAM_V = 64;   // å‚数值
    };
}
SourceCode/Bond/EAPSimulator/Resource.h
@@ -13,6 +13,7 @@
#define IDD_DIALOG_ADD_IDS              135
#define IDD_DIALOG_LINK_REPORT          137
#define IDD_DIALOG_LINK_REPORT_DETAIL   139
#define IDD_DIALOG_PJS                  141
#define IDC_EDIT_LOG                    1000
#define IDC_EDIT_IP                     1001
#define IDC_EDIT_PORT                   1002
@@ -51,14 +52,17 @@
#define IDC_BUTTON_CARRIER_RELEASE      1036
#define IDC_BUTTON_QUERY_CJ_SPACE       1037
#define IDC_BUTTON_QUERY_PJ_SPACE       1038
#define IDC_BUTTON_CREATE_PJ            1039
#define IDC_BUTTON_ADD                  1040
#define IDC_BUTTON_DELETE               1041
// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        141
#define _APS_NEXT_RESOURCE_VALUE        143
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1039
#define _APS_NEXT_CONTROL_VALUE         1042
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -363,7 +363,6 @@
        // æ¨¡æ‹Ÿæµ‹è¯•
        /*
        if (m_nIndex == 0) {
            static int ii = 0;
            ii++;
@@ -373,12 +372,25 @@
                CPortStatusReport portStatusReport;
                portStatusReport.setPortStatus(PORT_INUSE);
                portStatusReport.setJobExistenceSlot(0xf);
                portStatusReport.setCassetteId("CID1984113");
                portStatusReport.setCassetteId("CID1001");
                int nRet = portStatusReport.serialize(szBuffer, 64);
                decodePortStatusReport(pStep, szBuffer, 64);
            }
        }
        */
        if (m_nIndex == 1) {
            static int ii = 0;
            ii++;
            if (ii == 55) {
                char szBuffer[64] = { 0 };
                CStep* pStep = getStepWithName(STEP_EQ_PORT1_INUSE);
                CPortStatusReport portStatusReport;
                portStatusReport.setPortStatus(PORT_INUSE);
                portStatusReport.setJobExistenceSlot(0xf);
                portStatusReport.setCassetteId("CID1004");
                int nRet = portStatusReport.serialize(szBuffer, 64);
                decodePortStatusReport(pStep, szBuffer, 64);
            }
        }
    }
    void CLoadPort::serialize(CArchive& ar)
SourceCode/Bond/Servo/CMaster.cpp
@@ -3,6 +3,7 @@
#include "CMaster.h"
#include <future>
#include <vector>
#include "RecipeManager.h"
namespace SERVO {
@@ -62,6 +63,11 @@
    CMaster::~CMaster()
    {
        for (auto item : m_processJobs) {
            delete item;
        }
        m_processJobs.clear();
        if (m_hEventReadBitsThreadExit[0] != nullptr) {
            ::CloseHandle(m_hEventReadBitsThreadExit[0]);
            m_hEventReadBitsThreadExit[0] = nullptr;
@@ -1154,6 +1160,15 @@
        return nullptr;
    }
    CEquipment* CMaster::getEquipment(int id) const
    {
        for (auto item : m_listEquipment) {
            if (item->getID() == id) return item;
        }
        return nullptr;
    }
    /*
     * æ·»åŠ LoadPort1
     * index -- 0~3
@@ -1831,4 +1846,63 @@
    {
        m_nContinuousTransferCount = round;
    }
    int CMaster::setProcessJobs(std::vector<SERVO::CProcessJob*>& pjs)
    {
        std::vector<SERVO::CProcessJob*> temp;
        for (auto p : pjs) {
            if (p->validate(*this)) {
                temp.push_back(p);
            }
        }
        m_processJobs = temp;
        return m_processJobs.size();
    }
    CLoadPort* CMaster::getPortWithCarrierId(const std::string& carrierId) const
    {
        CLoadPort* pPort;
        int eqid[] = { EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4};
        for (int i = 0; i < 4; i++) {
            pPort = (CLoadPort*)getEquipment(eqid[i]);
            ASSERT(pPort);
            if (pPort->getCassetteId().compare(carrierId) == 0) return pPort;
        }
        return nullptr;
    }
    bool CMaster::isProcessJobsEmpty() const
    {
        return m_processJobs.empty();
    }
    bool CMaster::recipeExists(const std::string& ppid) const
    {
        std::vector<std::string> vecRecipe = RecipeManager::getInstance().getAllPPID();
        bool exists = std::find(vecRecipe.begin(), vecRecipe.end(), ppid) != vecRecipe.end();
        return exists;
    }
    bool CMaster::carrierPresent(const std::string& carrierId) const
    {
        CLoadPort* pPort = getPortWithCarrierId(carrierId);
        return pPort != nullptr;
    }
    bool CMaster::slotUsable(const std::string& carrierId, uint16_t slot) const
    {
        CLoadPort* pPort = getPortWithCarrierId(carrierId);
        if(pPort == nullptr) return false;
        CSlot* pSlot = pPort->getSlot(slot);
        if (pSlot == nullptr) return false;
        return pSlot->isEnable();
    }
    bool CMaster::ceidDefined(uint32_t ceid) const
    {
        return true;
    }
}
SourceCode/Bond/Servo/CMaster.h
@@ -13,7 +13,7 @@
#include "CArmTray.h"
#include "CCLinkIEControl.h"
#include "CRobotTask.h"
#include "ProcessJob.h"
#define CTStep_Unknow                   0
@@ -64,7 +64,7 @@
        ONCTROUNDEND            onCTRoundEnd;
    } MasterListener;
    class CMaster
    class CMaster : public IResourceView
    {
    public:
        CMaster();
@@ -87,6 +87,7 @@
        void onTimer(UINT nTimerid);
        std::list<CEquipment*>& getEquipmentList();
        CEquipment* getEquipment(int id);
        CEquipment* getEquipment(int id) const;
        void setCacheFilepath(const char* pszFilepath);
        int abortCurrentTask();
        int restoreCurrentTask();
@@ -104,6 +105,8 @@
        int carrierRelease(unsigned int port);
        int getContinuousTransferCount();
        void setContinuousTransferCount(int round);
        int setProcessJobs(std::vector<SERVO::CProcessJob*>& pjs);
        CLoadPort* getPortWithCarrierId(const std::string& carrierId) const;
    private:
        inline void lock() { EnterCriticalSection(&m_criticalSection); }
@@ -134,6 +137,14 @@
        CRobotTask* createTransferTask_restore(CEquipment* pEqSrc, CLoadPort** pPorts);
        CRobotTask* createTransferTask_continuous_transfer(CEquipment* pSrcEq, int nSrcSlot,
            CEquipment* pTarEq, int nTarSlot, int armNo = 1);
    public:
        // â€”— IResourceView è¦†å†™ â€”—(注意 const)
        bool isProcessJobsEmpty() const override;
        bool recipeExists(const std::string& ppid) const override;
        bool carrierPresent(const std::string& carrierId) const override;
        bool slotUsable(const std::string& carrierId, uint16_t slot) const override;
        bool ceidDefined(uint32_t ceid) const override;
    private:
        CRITICAL_SECTION m_criticalSection;
@@ -179,6 +190,7 @@
    private:
        bool m_bEnableEventReport;
        bool m_bEnableAlarmReport;
        std::vector<SERVO::CProcessJob*> m_processJobs;
    };
}
SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -612,6 +612,9 @@
        else if (nStream == 10 && pHeader->function == 3) {
            replyTerminalDisplay(pMessage);
        }
        else if (nStream == 16 && pHeader->function == 15) {
            replyPRJobMultiCreate(pMessage);
        }
    };
    PassiveListener listener;
@@ -1514,6 +1517,111 @@
    return 0;
}
// S16F15
int CHsmsPassive::replyPRJobMultiCreate(IMessage* pRecv)
{
    if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
        return ER_NOTSELECT;
    }
    ISECS2Item* pBody = pRecv->getBody();
    if (pBody == nullptr || pBody->getType() != SITYPE::L) ER_PARAM_ERROR;
    // è§£é‡Šæ•°æ®ï¼Œå¾—到CProcessJob
    ISECS2Item* pItemPjs, * pItemPj,* pItemCarriers, * pItemCarrier, *pItemSlots, *pItemRecipes;
    unsigned int DATAID;
    const char* pszPrjobid, *pszMF, *pszCarrierId, *pszRecipeName;
    std::string strCarrierId;
    unsigned int len;
    unsigned char slot, PRRECIPEMETHOD;
    std::vector<unsigned char> slots;
    std::vector<SERVO::CProcessJob*> pjs;
    if (!pBody->getSubItemU4(0, DATAID)) return ER_PARAM_ERROR;
    pItemPjs = pBody->getSubItem(1);
    if (pItemPjs == nullptr) return ER_PARAM_ERROR;
    for (int i = 0; i < pItemPjs->getSubItemSize(); i++) {
        pItemPj = pItemPjs->getSubItem(i);
        if (pItemPj == nullptr) continue;
        if (!pItemPj->getSubItemString(0, pszPrjobid)) continue;
        if (!pItemPj->getSubItemBinary(1, pszMF, len)) continue;
        pItemCarriers = pItemPj->getSubItem(2);
        if (pItemCarriers == nullptr) continue;
        pItemRecipes = pItemPj->getSubItem(3);
        if (pItemRecipes == nullptr) continue;
        SERVO::CProcessJob* pj = new SERVO::CProcessJob(pszPrjobid);
        int size = pItemCarriers->getSubItemSize();
        for (int j = 0; j < size; j++) {
            pItemCarrier = pItemCarriers->getSubItem(j);
            strCarrierId.clear();
            if (pItemCarrier->getSubItemString(0, pszCarrierId)) {
                strCarrierId = pszCarrierId;
            }
            slots.clear();
            pItemSlots = pItemCarrier->getSubItem(1);
            if (pItemSlots != nullptr) {
                int size2 = pItemSlots->getSubItemSize();
                for (int k = 0; k < size2; k++) {
                    if (pItemSlots->getSubItemU1(k, slot)) {
                        slots.push_back(slot);
                    }
                }
            }
            pj->addCarrier(strCarrierId, slots);
        }
        if (pItemRecipes->getSubItemU1(0, PRRECIPEMETHOD)
            && pItemRecipes->getSubItemString(1, pszRecipeName)) {
            pj->setRecipe(SERVO::RecipeMethod(PRRECIPEMETHOD), std::string(pszRecipeName));
        }
        pjs.push_back(pj);
    }
    ASSERT(m_listener.onPRJobMultiCreate != nullptr);
    int nRet = m_listener.onPRJobMultiCreate(this, pjs);
    // å›žå¤æŠ¥æ–‡
    IMessage* pMessage = NULL;
    HSMS_Create1Message(pMessage, m_nSessionId, 16, 16, ++m_nSystemByte);
    ASSERT(pMessage);
    ISECS2Item* pItemPrjobIds = pMessage->getBody()->addItem();
    ISECS2Item* pItemErrors = pMessage->getBody()->addItem();
    bool bHasError = false;
    for (auto p : pjs) {
        if (p->issue().empty()) {
            pItemPrjobIds->addItem(p->id().c_str(), "PRJOBID");
        }
        else {
            bHasError = true;
        }
    }
    if (bHasError) {
        pItemErrors->addBoolItem(false, "ACKA");
        ISECS2Item* pItemErrors2 = pItemErrors->addItem();
        for (auto p : pjs) {
            if (!p->issue().empty()) {
                ISECS2Item* pItemErr = pItemErrors2->addItem();
                pItemErr->addU4Item(p->issue()[0].code, "ERRCODE");
                pItemErr->addItem(("<" + p->id() + ">" + p->issue()[0].text).c_str(), "ERRTEXT");
            }
        }
    }
    m_pPassive->sendMessage(pMessage);
    LOGI("<HSMS>[SECS Msg SEND]S16F16 (SysByte=%u)", pMessage->getHeader()->systemBytes);
    HSMS_Destroy1Message(pMessage);
    // é‡Šæ”¾æœ‰é—®é¢˜(未添加到master)的内存
    for (auto p : pjs) {
        if(!p->issue().empty()) delete p;
    }
    pjs.clear();
    return 0;
}
// S5F1
int CHsmsPassive::requestAlarmReport(int ALCD, int ALID, const char* ALTX)
{
SourceCode/Bond/Servo/HsmsPassive.h
@@ -7,6 +7,7 @@
#include <map>
#include <set>
#include "CCollectionEvent.h"
#include "ProcessJob.h"
#define EQCONSTANT_VALUE_MAX    64
@@ -86,6 +87,7 @@
    const char* pszCarrierId,
    unsigned char PTN, 
    std::string& strErrorTxt)> CARRIERACTION;
typedef std::function<int(void* pFrom, std::vector<SERVO::CProcessJob*>& pjs)> PRJOBMULTICREATE;
typedef struct _SECSListener
{
    SECSEQOFFLINE                onEQOffLine;
@@ -98,6 +100,7 @@
    EDALARMREPORT                onEnableDisableAlarmReport;
    QUERYPPIDLIST                onQueryPPIDList;
    CARRIERACTION                onCarrierAction;
    PRJOBMULTICREATE            onPRJobMultiCreate;
} SECSListener;
@@ -207,6 +210,7 @@
    int replyPurgeSpooledData(IMessage* pRecv);
    int replyQueryPPIDList(IMessage* pRecv);
    int replyTerminalDisplay(IMessage* pRecv);
    int replyPRJobMultiCreate(IMessage* pRecv);
private:
    inline void Lock() { EnterCriticalSection(&m_criticalSection); }
SourceCode/Bond/Servo/Model.cpp
@@ -175,6 +175,12 @@
            return CAACK_5;
            LOGI("<Model>onCarrierAction %d, %s, %d, %d", DATAID, pszCarrierAction, pszCarrierId, PTN);
    };
    listener.onPRJobMultiCreate = [&](void* pFrom, std::vector<SERVO::CProcessJob*>& pjs) -> int {
        for (auto p : pjs) {
            LOGI("<Model>onPRJobMultiCreate %s %s", p->id().c_str(), p->recipeSpec().c_str());
        }
        return m_master.setProcessJobs(pjs);
    };
    m_hsmsPassive.setListener(listener);
    m_hsmsPassive.setEquipmentModelType((LPTSTR)(LPCTSTR)strModeType);
    m_hsmsPassive.setSoftRev((LPTSTR)(LPCTSTR)strSoftRev);
SourceCode/Bond/Servo/ProcessJob.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,260 @@
#include "stdafx.h"
#include "ProcessJob.h"
#include <cctype>
namespace SERVO {
    static inline std::string trimCopy(std::string s) {
        auto notspace = [](int ch) { return !std::isspace(ch); };
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), notspace));
        s.erase(std::find_if(s.rbegin(), s.rend(), notspace).base(), s.end());
        return s;
    }
    CProcessJob::CProcessJob(std::string pjId)
        : m_pjId(trimCopy(pjId))
    {
        clampString(m_pjId, MAX_ID_LEN);
    }
    void CProcessJob::setParentCjId(std::string cjId) {
        m_parentCjId = trimCopy(cjId);
        clampString(m_parentCjId, MAX_ID_LEN);
    }
    void CProcessJob::setRecipe(RecipeMethod method, std::string spec) {
        m_recipeMethod = method;
        m_recipeSpec = trimCopy(spec);
        clampString(m_recipeSpec, MAX_ID_LEN);
    }
    void CProcessJob::addParam(std::string name, std::string value) {
        name = trimCopy(name);
        value = trimCopy(value);
        clampString(name, MAX_PARAM_K);
        clampString(value, MAX_PARAM_V);
        m_params.push_back({ std::move(name), std::move(value) });
    }
    void CProcessJob::setParams(std::vector<PJParam> params) {
        m_params.clear();
        m_params.reserve(params.size());
        for (auto& p : params) addParam(std::move(p.name), std::move(p.value));
    }
    void CProcessJob::addPauseEvent(uint32_t ceid) {
        if (ceid) m_pauseEvents.push_back(ceid);
        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
    }
    void CProcessJob::setPauseEvents(std::vector<uint32_t> ceids) {
        m_pauseEvents = std::move(ceids);
        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
    }
    const std::vector<CProcessJob::ValidationIssue>& CProcessJob::issue()
    {
        return m_issues;
    }
    bool CProcessJob::validate(const IResourceView& rv)
    {
        m_issues.clear();
        // è®© add åŒæ—¶æ”¯æŒ const char* å’Œ std::string
        auto add = [&](uint32_t code, std::string msg) {
            m_issues.push_back({ code, std::move(msg) });
        };
        if (!rv.isProcessJobsEmpty()) {
            add(1000, "ProcessJobs Conflict!");
        }
        // â€”— åŸºæœ¬ / æ ‡è¯† â€”—
        if (m_pjId.empty())            add(1001, "PJID empty");
        if (!asciiPrintable(m_pjId))   add(1002, "PJID has non-printable chars");
        // if (m_parentCjId.empty())      add(1010, "Parent CJID empty");
        // â€”— é…æ–¹ï¼ˆRCPSPEC / PPID)——
        if (m_recipeSpec.empty())      add(1020, "Recipe spec (PPID) empty");
        else if (!rv.recipeExists(m_recipeSpec)) {
            add(1021, "PPID not found: " + m_recipeSpec);
        }
        // â€”— é…æ–¹æ–¹æ³• vs å‚æ•° â€”— 1=NoTuning ç¦æ­¢å¸¦å‚æ•°ï¼›2=WithTuning å…è®¸/可选
        if (m_recipeMethod == RecipeMethod::NoTuning && !m_params.empty()) {
            add(1022, "Params not allowed when PRRECIPEMETHOD=1 (NoTuning)");
        }
        // â€”— ç‰©æ–™é€‰æ‹©æ ¡éªŒ â€”—(二选一:Carrier+Slots æˆ– MIDs;两者都不填则错误)
        const bool hasCarrierSlots = !m_carriers.empty();
        if (hasCarrierSlots) {
            // {L:n { CARRIERID {L:j SLOTID} }}
            for (const auto& cs : m_carriers) {
                if (cs.carrierId.empty()) {
                    add(1030, "CarrierID empty");
                    continue;
                }
                if (!rv.carrierPresent(cs.carrierId)) {
                    add(1031, "Carrier not present: " + cs.carrierId);
                }
                if (cs.slots.empty()) {
                    add(1032, "No slots specified for carrier: " + cs.carrierId);
                }
                for (auto s : cs.slots) {
                    if (s == 0) {
                        add(1033, "Slot 0 is invalid for carrier: " + cs.carrierId);
                        continue;
                    }
                    if (!rv.slotUsable(cs.carrierId, s)) {
                        add(1034, "Slot unusable: carrier=" + cs.carrierId + " slot=" + std::to_string(s));
                    }
                }
            }
        }
        else {
            add(1035, "No material selection provided (neither Carrier/Slots nor MIDs)");
        }
        // â€”— æš‚停事件(PRPAUSEEVENTID åˆ—表)——
        for (auto ceid : m_pauseEvents) {
            if (!rv.ceidDefined(ceid)) {
                add(1050, "Pause CEID unknown: " + std::to_string(ceid));
            }
        }
        return m_issues.empty();
    }
    // â€”— çŠ¶æ€æœº â€”—
    // è§„则可按你们协议微调
    bool CProcessJob::queue() {
        if (m_state != PJState::NoState) return false;
        markQueued();
        return true;
    }
    bool CProcessJob::enterSettingUp() {
        if (m_state != PJState::Queued) return false;
        m_state = PJState::SettingUp;
        return true;
    }
    bool CProcessJob::start() {
        if (m_state != PJState::Queued && m_state != PJState::SettingUp && m_state != PJState::Paused)
            return false;
        if (!m_tStart.has_value()) markStart();
        m_state = PJState::InProcess;
        return true;
    }
    bool CProcessJob::pause() {
        if (m_state != PJState::InProcess) return false;
        m_state = PJState::Paused;
        return true;
    }
    bool CProcessJob::resume() {
        if (m_state != PJState::Paused) return false;
        m_state = PJState::InProcess;
        return true;
    }
    bool CProcessJob::complete() {
        if (m_state != PJState::InProcess && m_state != PJState::Paused) return false;
        m_state = PJState::Completed;
        markEnd();
        return true;
    }
    bool CProcessJob::abort() {
        if (m_state == PJState::Completed || m_state == PJState::Aborted || m_state == PJState::Failed)
            return false;
        m_state = PJState::Aborted;
        markEnd();
        return true;
    }
    bool CProcessJob::fail(std::string reason) {
        m_failReason = trimCopy(reason);
        clampString(m_failReason, 128);
        m_state = PJState::Failed;
        markEnd();
        return true;
    }
    // â€”— æ—¶é—´æˆ³ & å·¥å…· â€”—
    void CProcessJob::markQueued() {
        m_state = PJState::Queued;
        m_tQueued = std::chrono::system_clock::now();
    }
    void CProcessJob::markStart() {
        m_tStart = std::chrono::system_clock::now();
    }
    void CProcessJob::markEnd() {
        m_tEnd = std::chrono::system_clock::now();
    }
    void CProcessJob::clampString(std::string& s, size_t maxLen) {
        if (s.size() > maxLen) s.resize(maxLen);
    }
    bool CProcessJob::asciiPrintable(const std::string& s) {
        return std::all_of(s.begin(), s.end(), [](unsigned char c) {
            return c >= 0x20 && c <= 0x7E;
            });
    }
    void CProcessJob::setCarriers(std::vector<CarrierSlotInfo> carriers)
    {
        // ç»Ÿä¸€é€šè¿‡ addCarrier åšè§„范化(去空白、截断、去重、合并同 carrier)
        m_carriers.clear();
        m_carriers.reserve(carriers.size());
        for (auto& cs : carriers) {
            addCarrier(std::move(cs.carrierId), std::move(cs.slots));
        }
    }
    void CProcessJob::addCarrier(std::string carrierId, std::vector<uint8_t> slots)
    {
        // 1) è§„范化 carrierId:去空白 + é•¿åº¦é™åˆ¶
        carrierId = trimCopy(std::move(carrierId));
        clampString(carrierId, MAX_ID_LEN);
        if (carrierId.empty()) {
            // ç©º ID ç›´æŽ¥å¿½ç•¥ï¼ˆä¹Ÿå¯ä»¥é€‰æ‹©æŠ›å¼‚常/记录日志,看你项目风格)
            return;
        }
        // 2) è§„范化 slots:去 0、排序、去重
        //    æ³¨ï¼šSLOTID æŒ‰ 1..N,0 è§†ä¸ºéžæ³•/占位
        slots.erase(std::remove(slots.begin(), slots.end(), 0), slots.end());
        std::sort(slots.begin(), slots.end());
        slots.erase(std::unique(slots.begin(), slots.end()), slots.end());
        if (slots.empty()) {
            // æ²¡æœ‰æœ‰æ•ˆå¡ä½å°±ä¸è¿½åŠ 
            return;
        }
        // 3) å¦‚果已存在同名载具,则合并 slot åˆ—表
        auto it = std::find_if(m_carriers.begin(), m_carriers.end(),
            [&](const CarrierSlotInfo& cs) { return cs.carrierId == carrierId; });
        if (it != m_carriers.end()) {
            // åˆå¹¶
            it->slots.insert(it->slots.end(), slots.begin(), slots.end());
            std::sort(it->slots.begin(), it->slots.end());
            it->slots.erase(std::unique(it->slots.begin(), it->slots.end()), it->slots.end());
        }
        else {
            // æ–°å¢ž
            CarrierSlotInfo cs;
            cs.carrierId = std::move(carrierId);
            cs.slots = std::move(slots);
            m_carriers.emplace_back(std::move(cs));
        }
    }
}
SourceCode/Bond/Servo/ProcessJob.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,203 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <cstdint>
#include <chrono>
#include <optional>
namespace SERVO {
    /// PJ ç”Ÿå‘½å‘¨æœŸï¼ˆè´´è¿‘ E40 å¸¸è§çŠ¶æ€ï¼‰
    enum class PJState : uint8_t {
        NoState = 0,
        Queued,
        SettingUp,
        InProcess,
        Paused,
        Aborting,
        Completed,
        Aborted,
        Failed
    };
    /// é…æ–¹æŒ‡å®šæ–¹å¼ï¼ˆå¯¹åº” S16F15 é‡Œ PRRECIPEMETHOD)
    enum class RecipeMethod : uint8_t {
        NoTuning = 1,   // 1 - recipe without variable tuning
        WithTuning = 2  // 2 - recipe with variable tuning
    };
    /// å¯åŠ¨ç­–ç•¥ï¼ˆå¯¹åº” S16F15 é‡Œ PRPROCESSSTART)
    enum class StartPolicy : uint8_t {
        Queued = 0,   // å»ºç«‹åŽæŽ’队
        AutoStart = 1 // æ¡ä»¶æ»¡è¶³åˆ™è‡ªåŠ¨å¯åŠ¨
    };
    /** é…æ–¹å‚数对(S16F15 ä¸­ RCPPARNM / RCPPARVAL) */
    struct PJParam {
        std::string name;   // RCPPARNM
        std::string value;  // RCPPARVAL
    };
    /**
    {L:2
        CARRIERID
        {L:j
            SLOTID
        }
    }
     */
    struct CarrierSlotInfo {
        std::string carrierId;              // CARRIERID
        std::vector<uint8_t> slots;        // SLOTID[]
    };
    /// ç®€å•资源视图接口:供 Validate() æŸ¥è¯¢ï¼ˆç”±è®¾å¤‡ç«¯å®žçŽ°è€…åœ¨å¤–éƒ¨æä¾›ï¼‰
    struct IResourceView {
        virtual ~IResourceView() = default;
        virtual bool isProcessJobsEmpty() const = 0;
        virtual bool recipeExists(const std::string& ppid) const = 0;
        virtual bool carrierPresent(const std::string& carrierId) const = 0;
        virtual bool slotUsable(const std::string& carrierId, uint16_t slot) const = 0;
        virtual bool ceidDefined(uint32_t ceid) const = 0;
        // ä½ ä¹Ÿå¯ä»¥æ‰©å±•:port状态、占用情况、CJ/PJ空间等
    };
    /// PJ ä¸»ç±»
    /**
     * ProcessJob â€”— ä¸Ž S16F15(PRJobMultiCreate)字段一一对应的承载类
     *
     * S16F15 ç»“构(核心节选):
     * {L:6
     *   PRJOBID                -> m_pjId
     *   MF                     -> m_mf
     *   {L:n { CARRIERID {L:j SLOTID} } }
     *   {L:3
     *     PRRECIPEMETHOD       -> m_recipeType
     *     RCPSPEC(PPID)      -> m_recipeSpec
     *     {L:m { RCPPARNM RCPPARVAL }}     -> m_params
     *   }
     *   PRPROCESSSTART         -> m_startPolicy
     *   {L:k PRPAUSEEVENTID}   -> m_pauseEvents
     * }
     */
    class CProcessJob {
    public:
        // â€”— æž„造 / åŸºæœ¬è®¾ç½® â€”—
        explicit CProcessJob(std::string pjId);
        const std::string& id() const noexcept { return m_pjId; }
        const std::string& parentCjId() const noexcept { return m_parentCjId; }
        PJState state() const noexcept { return m_state; }
        StartPolicy startPolicy() const noexcept { return m_startPolicy; }
        RecipeMethod recipeMethod() const noexcept { return m_recipeMethod; }
        const std::string& recipeSpec() const noexcept { return m_recipeSpec; } // PPID æˆ– Spec
        // ç»‘定父 CJ
        void setParentCjId(std::string cjId);
        // é…æ–¹
        void setRecipe(RecipeMethod method, std::string spec);
        // å¯åŠ¨ç­–ç•¥
        void setStartPolicy(StartPolicy sp) { m_startPolicy = sp; }
        // å‚æ•°
        void addParam(std::string name, std::string value);
        void setParams(std::vector<PJParam> params);
        // æš‚停事件
        void addPauseEvent(uint32_t ceid);
        void setPauseEvents(std::vector<uint32_t> ceids);
        // â€”— æ ¡éªŒ â€”—
        struct ValidationIssue {
            uint32_t code;      // è‡ªå®šä¹‰é”™è¯¯ç 
            std::string text;   // æ–‡æœ¬æè¿°
        };
        // è¿”回问题清单(空=通过)
        bool validate(const IResourceView& rv);
        const std::vector<ValidationIssue>& issue();
        // â€”— çŠ¶æ€æœºï¼ˆå¸¦å®ˆå«ï¼‰â€”â€”
        bool queue();           // NoState -> Queued
        bool start();           // Queued/SettingUp -> InProcess
        bool enterSettingUp();  // Queued -> SettingUp
        bool pause();           // InProcess -> Paused
        bool resume();          // Paused -> InProcess
        bool complete();        // InProcess -> Completed
        bool abort();           // Any (未终态) -> Aborted
        bool fail(std::string reason); // ä»»æ„æ€ -> Failed(记录失败原因)
        // â€”— è®¿é—®å™¨ï¼ˆç”¨äºŽä¸ŠæŠ¥/查询)——
        const std::vector<PJParam>& params() const noexcept { return m_params; }
        const std::vector<uint32_t>& pauseEvents() const noexcept { return m_pauseEvents; }
        const std::string& failReason() const noexcept { return m_failReason; }
        // æ—¶é—´æˆ³ï¼ˆå¯ç”¨äºŽæŠ¥è¡¨/追溯)
        std::optional<std::chrono::system_clock::time_point> tQueued() const { return m_tQueued; }
        std::optional<std::chrono::system_clock::time_point> tStart()  const { return m_tStart; }
        std::optional<std::chrono::system_clock::time_point> tEnd()    const { return m_tEnd; }
        // é•¿åº¦é™åˆ¶å·¥å…·ï¼ˆå¯åœ¨é›†æˆæ—¶ç»Ÿä¸€ç­–略)
        static void clampString(std::string& s, size_t maxLen);
        static bool asciiPrintable(const std::string& s);
        // æ¸…空并整体设置
        void setCarriers(std::vector<CarrierSlotInfo> carriers);
        // è¿½åŠ ä¸€ä¸ªè½½å…·
        void addCarrier(std::string carrierId, std::vector<uint8_t> slots);
        // è®¿é—®å™¨
        const std::vector<CarrierSlotInfo>& carriers() const noexcept { return m_carriers; }
        // åˆ¤å®šæ˜¯å¦â€œæŒ‰è½½å…·/卡位”方式
        bool usesCarrierSlots() const noexcept { return !m_carriers.empty(); }
    private:
        // å†…部状态转移帮助
        void markQueued();
        void markStart();
        void markEnd();
    private:
        // æ ‡è¯†
        std::string m_pjId;
        std::string m_parentCjId;
        // é…æ–¹
        RecipeMethod m_recipeMethod{ RecipeMethod::NoTuning };
        std::string  m_recipeSpec; // PPID / Spec
        // ç‰©æ–™
        static constexpr uint8_t MATERIAL_FORMAT = 14; // substrate
        std::vector<CarrierSlotInfo> m_carriers;   // {L:n { CARRIERID {L:j SLOTID} }}
        // å‚æ•° / æš‚停事件
        std::vector<PJParam>    m_params;
        std::vector<uint32_t>   m_pauseEvents;
        // çŠ¶æ€ & è®°å½•
        StartPolicy m_startPolicy{ StartPolicy::Queued }; // 0=Queued, 1=AutoStart
        PJState m_state{ PJState::NoState };
        std::string m_failReason;
        // æ—¶é—´æˆ³
        std::optional<std::chrono::system_clock::time_point> m_tQueued;
        std::optional<std::chrono::system_clock::time_point> m_tStart;
        std::optional<std::chrono::system_clock::time_point> m_tEnd;
        // çº¦æŸï¼ˆå¯æŒ‰ä½ ä»¬åè®®è°ƒæ•´ï¼‰
        static constexpr size_t MAX_ID_LEN = 64;   // PJID/ CJID/ CarrierID/ MID/ PPID
        static constexpr size_t MAX_PARAM_K = 32;   // å‚数名
        static constexpr size_t MAX_PARAM_V = 64;   // å‚数值
        // é”™è¯¯åˆ—表
        std::vector<ValidationIssue> m_issues;
    };
}
SourceCode/Bond/Servo/Servo.vcxproj
@@ -117,6 +117,7 @@
      <PreprocessorDefinitions>_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <SDLCheck>true</SDLCheck>
      <AdditionalIncludeDirectories>.;..;..\DatabaseSDK\include;..\MELSECSDK\include;.\CCLinkPerformance;.\GridControl;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <LanguageStandard>stdcpp17</LanguageStandard>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
@@ -324,6 +325,7 @@
    <ClInclude Include="PageRobotCmd.h" />
    <ClInclude Include="PageTransferLog.h" />
    <ClInclude Include="PortConfigurationDlg.h" />
    <ClInclude Include="ProcessJob.h" />
    <ClInclude Include="ProductionLogManager.h" />
    <ClInclude Include="RecipeDeviceBindDlg.h" />
    <ClInclude Include="RecipeManager.h" />
@@ -470,6 +472,7 @@
    <ClCompile Include="PageRobotCmd.cpp" />
    <ClCompile Include="PageTransferLog.cpp" />
    <ClCompile Include="PortConfigurationDlg.cpp" />
    <ClCompile Include="ProcessJob.cpp" />
    <ClCompile Include="ProductionLogManager.cpp" />
    <ClCompile Include="RecipeDeviceBindDlg.cpp" />
    <ClCompile Include="RecipeManager.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -176,6 +176,7 @@
    <ClCompile Include="CPageVarialbles.cpp" />
    <ClCompile Include="CPageReport.cpp" />
    <ClCompile Include="CPageCollectionEvent.cpp" />
    <ClCompile Include="ProcessJob.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -357,6 +358,7 @@
    <ClInclude Include="CPageVarialbles.h" />
    <ClInclude Include="CPageReport.h" />
    <ClInclude Include="CPageCollectionEvent.h" />
    <ClInclude Include="ProcessJob.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />