mrDarker
2025-08-26 43c7dc211f10851480352b12bd01f3443079bb01
Merge branch 'clh' into liuyang
已添加15个文件
已修改30个文件
3645 ■■■■■ 文件已修改
Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.h 14 ●●●●● 补丁 | 查看 | 原始文档 | 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 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/ProcessJob.cpp 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/ProcessJob.h 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/Resource.h 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJob.cpp 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJob.h 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobDlg.cpp 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobDlg.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.cpp 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVariable.cpp 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVariable.h 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.cpp 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.h 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/SerializeUtil.h 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/TopToolbar.cpp 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/TopToolbar.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/CollectionEventList.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/ReportList.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/Res/ControlJob_Gray_32.ico 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/Res/ControlJob_High_32.ico 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/VariableList.txt 4 ●●● 补丁 | 查看 | 原始文档 | 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 = {};
@@ -69,6 +71,10 @@
        if (nStream == 5 && pHeader->function == 1) {
            // S5F1
            replyAck(5, 2, pMessage->getHeader()->systemBytes, 0, _T("ACK0"));
        }
        else if (nStream == 6 && pHeader->function == 11) {
            // S5F1
            replyAck(6, 12, pMessage->getHeader()->systemBytes, 0, _T("ACK0"));
        }
    };
@@ -289,6 +295,18 @@
    return 0;
}
int CHsmsActive::hsmsSelectedEquipmentStatusRequest(unsigned int SVID)
{
    IMessage* pMessage = nullptr;
    int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 3, ++m_nSystemByte);
    pMessage->getBody()->addU4Item(SVID, "SVID");
    m_pActive->sendMessage(pMessage);
    HSMS_Destroy1Message(pMessage);
    return 0;
}
int CHsmsActive::hsmsQueryPPIDList()
{
    IMessage* pMessage = nullptr;
@@ -331,6 +349,86 @@
    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::hsmsCreateControlJob(const char* pszControlJobId, std::vector<std::string>& processJobIds)
{
    char szBuffer[256];
    sprintf_s(szBuffer, 256, "ControlJob:%s>", pszControlJobId);
    IMessage* pMessage = nullptr;
    int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 14 | REPLY, 9, ++m_nSystemByte);
    pMessage->getBody()->addItem(szBuffer, "OBJSPEC");
    pMessage->getBody()->addItem("ControlJob", "OBJTYPE");
    auto itemAttrs = pMessage->getBody()->addItem();
    {
        auto itemAttr = itemAttrs->addItem();
        itemAttr->addItem("Priority", "ATTRID");
        itemAttr->addU1Item(8, "ATTRDATA");
    }
    {
        auto itemAttr = itemAttrs->addItem();
        itemAttr->addItem("weight", "ATTRID");
        itemAttr->addF4Item(60.5, "ATTRDATA");
    }
    {
        auto itemAttr = itemAttrs->addItem();
        itemAttr->addItem("tel", "ATTRID");
        itemAttr->addItem("15919875007", "ATTRDATA");
    }
    {
        auto itemAttr = itemAttrs->addItem();
        itemAttr->addItem("PRJOBLIST", "ATTRID");
        auto itemProcessJobs = itemAttr->addItem();
        for (auto& item : processJobIds) {
            itemProcessJobs->addItem(item.c_str(), "");
        }
    }
    m_pActive->sendMessage(pMessage);
    HSMS_Destroy1Message(pMessage);
    return 0;
}
int CHsmsActive::replyAck0(IMessage* pMessage)
{
    return 0;
SourceCode/Bond/EAPSimulator/CHsmsActive.h
@@ -4,6 +4,12 @@
#include <map>
#include <set>
#include "CCollectionEvent.h"
#include "ProcessJob.h"
#define SVID_CJobSpace                5001
#define SVID_PJobSpace                5002
#define SVID_PJobQueued                5003
typedef std::function<void(void* pFrom, ACTIVESTATE state)> STATECHANGED;
@@ -61,6 +67,9 @@
    int hsmsTransmitSpooledData();
    int hsmsPurgeSpooledData();
    // æŸ¥è¯¢å˜é‡
    int hsmsSelectedEquipmentStatusRequest(unsigned int SVID);
    // æŸ¥è¯¢PPID List
    int hsmsQueryPPIDList();
@@ -77,6 +86,11 @@
        const char* pszCarrierId,
        unsigned char PTN);
    // S16F15
    int hsmsPRJobMultiCreate(std::vector<SERVO::CProcessJob*>& pjs);
    // S14F9
    int hsmsCreateControlJob(const char* pszControlJobId, std::vector<std::string>& processJobIds);
    // é€šè¿‡çš„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
@@ -90,6 +91,10 @@
    ON_BN_CLICKED(IDC_BUTTON_QUERY_PPID_LIST, &CEAPSimulatorDlg::OnBnClickedButtonQueryPpidList)
    ON_BN_CLICKED(IDC_BUTTON_PROCEED_WITH_CARRIER, &CEAPSimulatorDlg::OnBnClickedButtonProceedWithCarrier)
    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)
    ON_BN_CLICKED(IDC_BUTTON_CREATE_CJ, &CEAPSimulatorDlg::OnBnClickedButtonCreateCj)
END_MESSAGE_MAP()
@@ -279,6 +284,10 @@
    GetDlgItem(IDC_BUTTON_QUERY_PPID_LIST)->EnableWindow(enabled);    
    GetDlgItem(IDC_BUTTON_PROCEED_WITH_CARRIER)->EnableWindow(enabled);    
    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);
    GetDlgItem(IDC_BUTTON_CREATE_CJ)->EnableWindow(enabled);
}
void CEAPSimulatorDlg::OnBnClickedButtonConnect()
@@ -376,6 +385,16 @@
    theApp.m_model.m_pHsmsActive->hsmsPurgeSpooledData();
}
void CEAPSimulatorDlg::OnBnClickedButtonQueryCjSpace()
{
    theApp.m_model.m_pHsmsActive->hsmsSelectedEquipmentStatusRequest(SVID_CJobSpace);
}
void CEAPSimulatorDlg::OnBnClickedButtonQueryPjSpace()
{
    theApp.m_model.m_pHsmsActive->hsmsSelectedEquipmentStatusRequest(SVID_PJobQueued);
}
void CEAPSimulatorDlg::OnBnClickedButtonQueryPpidList()
{
    theApp.m_model.m_pHsmsActive->hsmsQueryPPIDList();
@@ -391,3 +410,15 @@
{
    theApp.m_model.m_pHsmsActive->hsmsCarrierRelease(DATAID++, "CSX 52078", 2);
}
void CEAPSimulatorDlg::OnBnClickedButtonCreatePj()
{
    CPJsDlg dlg;
    dlg.DoModal();
}
void CEAPSimulatorDlg::OnBnClickedButtonCreateCj()
{
    std::vector<std::string> processJobIds = {"PJ0001", "PJ0003"};
    theApp.m_model.m_pHsmsActive->hsmsCreateControlJob("CJ5007", processJobIds);
}
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
@@ -59,4 +59,8 @@
    afx_msg void OnBnClickedButtonQueryPpidList();
    afx_msg void OnBnClickedButtonProceedWithCarrier();
    afx_msg void OnBnClickedButtonCarrierRelease();
    afx_msg void OnBnClickedButtonQueryCjSpace();
    afx_msg void OnBnClickedButtonQueryPjSpace();
    afx_msg void OnBnClickedButtonCreatePj();
    afx_msg void OnBnClickedButtonCreateCj();
};
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
@@ -46,18 +47,24 @@
#define IDC_EDIT_CE_NAME                1031
#define IDC_BUTTON_QUERY_PPID_LIST      1032
#define IDC_EDIT_CE_RPTID               1033
#define IDC_BUTTON_PROCEED_WITH_CARRIER 1033
#define IDC_BUTTON_TRANSMIT_SPOOLED_DATA 1034
#define IDC_BUTTON_PROCEED_WITH_CARRIER2 1035
#define IDC_BUTTON_CARRIER_RELEASE      1035
#define IDC_BUTTON_PROCEED_WITH_CARRIER 1034
#define IDC_BUTTON_TRANSMIT_SPOOLED_DATA 1035
#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_CREATE_PJ2           1040
#define IDC_BUTTON_CREATE_CJ            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         1035
#define _APS_NEXT_CONTROL_VALUE         1042
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif
SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h
@@ -70,5 +70,6 @@
    virtual void setBinary(const char* pszData, unsigned int len, const char* pszNote) = 0;
    virtual void setString(const char* pszText, const char* pszNote) = 0;
    virtual void setU1(unsigned char value, const char* pszNote) = 0;
    virtual void setBool(bool value, const char* pszNote) = 0;
    virtual ISECS2Item* addItem() = 0;
};
SourceCode/Bond/Servo/CControlJob.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,336 @@
#include "stdafx.h"
#include "CControlJob.h"
#include <cctype>
#include "SerializeUtil.h"
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;
}
namespace SERVO {
    CControlJob::CControlJob()
    {
    }
    CControlJob::CControlJob(std::string cjId)
        : m_cjId(trimCopy(std::move(cjId)))
    {
        clampString(m_cjId, MAX_ID_LEN);
    }
    CControlJob::CControlJob(CControlJob& src)
    {
        m_cjId = src.m_cjId;
        clampString(m_cjId, MAX_ID_LEN);
        m_priority = src.m_priority;
        m_pjIds = src.m_pjIds;
        m_state = src.m_state;
        m_failReason = src.m_failReason;
        m_tQueued = src.m_tQueued;
        m_tStart = src.m_tStart;
        m_tEnd = src.m_tEnd;
    }
    bool CControlJob::addPJ(const std::string& pjId) {
        if (pjId.empty()) return false;
        auto id = pjId;
        auto it = std::find(m_pjIds.begin(), m_pjIds.end(), id);
        if (it != m_pjIds.end()) return false;
        clampString(id, MAX_ID_LEN);
        m_pjIds.push_back(std::move(id));
        return true;
    }
    bool CControlJob::addPJs(const std::vector<std::string>& ids) {
        bool added = false;
        for (auto& s : ids) added |= addPJ(s);
        return added;
    }
    bool CControlJob::setPJs(const std::vector<CProcessJob*>& pjs)
    {
        m_pjs = pjs;
        return true;
    }
    bool CControlJob::removePJ(const std::string& pjId) {
        auto it = std::find(m_pjIds.begin(), m_pjIds.end(), pjId);
        if (it == m_pjIds.end()) return false;
        m_pjIds.erase(it);
        return true;
    }
    bool CControlJob::containsPJ(const std::string& pjId) const {
        return std::find(m_pjIds.begin(), m_pjIds.end(), pjId) != m_pjIds.end();
    }
    const std::vector<CControlJob::ValidationIssue>& CControlJob::issues()
    {
        return m_issues;
    }
    bool CControlJob::validateForCreate(
            const std::function<bool(uint32_t& code, std::string& msg)>& canCreateCjFn,
            const std::function<bool(const std::string&)>& getPjExistsFn,
            const std::function<bool(const std::string&)>& canJoinFn
        )
    {
        m_issues.clear();
        auto add = [&](uint32_t code, std::string msg) { m_issues.push_back({ code, std::move(msg) }); };
        // æ˜¯å¦èƒ½åˆ›å»ºCJ, ç”±ä¸Šå±‚根据当前任务,机器状态等检验
        uint32_t cc;
        std::string mm;
        if (!canCreateCjFn(cc, mm)) {
            add(cc, mm);
        }
        // CJID åŸºç¡€æ ¡éªŒ
        if (m_cjId.empty())           add(1101, "CJID empty");
        if (!asciiPrintable(m_cjId))  add(1102, "CJID has non-printable chars");
        // PJ åˆ—表校验
        if (m_pjIds.empty())          add(1110, "PRJOBLIST empty");
        for (const auto& pj : m_pjIds) {
            if (!getPjExistsFn(pj))   add(1111, "PJ not found: " + pj);
            else if (!canJoinFn(pj))  add(1112, "PJ not joinable: " + pj);
        }
        return m_issues.empty();
    }
    // åº”用创建/更新(用于 S14F9 â†’ S14F10 è·¯å¾„)
    CControlJob::CreateResult
        CControlJob::applyCreate(
            const CreateRequest& req,
            const std::function<bool(const std::string&)>& getPjExistsFn,
            const std::function<bool(const std::string&)>& canJoinFn
        )
    {
        CreateResult r;
        // è¦†ç›–优先级(如提供)
        if (req.priority.has_value()) {
            m_priority = *req.priority;
        }
        // é€ PJ åˆ¤å®š
        for (const auto& pjIdRaw : req.requestedPjIds) {
            std::string pjId = trimCopy(pjIdRaw);
            clampString(pjId, MAX_ID_LEN);
            if (!getPjExistsFn(pjId)) {
                r.errors.push_back({ 2001, "PRJOBLIST: " + pjId + " not found" });
                continue;
            }
            if (!canJoinFn(pjId)) {
                r.errors.push_back({ 2002, "PRJOBLIST: " + pjId + " not joinable (state)" });
                continue;
            }
            if (containsPJ(pjId)) {
                // å·²åœ¨åˆ—表,视作成功(幂等)
                r.acceptedPjIds.push_back(pjId);
                continue;
            }
            // åŠ å…¥ CJ
            m_pjIds.push_back(pjId);
            r.acceptedPjIds.push_back(std::move(pjId));
        }
        // å½’å¹¶ ACK
        if (r.errors.empty())               r.objack = 0; // å…¨æˆåŠŸ
        else if (!r.acceptedPjIds.empty())  r.objack = 1; // éƒ¨åˆ†æˆåŠŸ
        else                                r.objack = 2; // å…¨å¤±è´¥
        return r;
    }
    // â€”— çŠ¶æ€æœº â€”— //
    bool CControlJob::queue() {
        if (m_state != CJState::NoState) return false;
        markQueued();
        return true;
    }
    bool CControlJob::start() {
        if (m_state != CJState::Queued) return false;
        m_state = CJState::Executing;
        if (!m_tStart.has_value()) markStart();
        return true;
    }
    bool CControlJob::pause() {
        if (m_state != CJState::Executing) return false;
        m_state = CJState::Paused;
        return true;
    }
    bool CControlJob::resume() {
        if (m_state != CJState::Paused) return false;
        m_state = CJState::Executing;
        return true;
    }
    bool CControlJob::complete() {
        if (m_state != CJState::Executing && m_state != CJState::Paused) return false;
        m_state = CJState::Completed;
        markEnd();
        return true;
    }
    bool CControlJob::abort() {
        if (m_state == CJState::Completed || m_state == CJState::Aborted || m_state == CJState::Failed)
            return false;
        m_state = CJState::Aborted;
        markEnd();
        return true;
    }
    bool CControlJob::fail(std::string reason) {
        m_failReason = trimCopy(reason);
        clampString(m_failReason, 128);
        m_state = CJState::Failed;
        markEnd();
        return true;
    }
    // â€”— èšåˆå®Œæˆåˆ¤æ–­ â€”— //
    bool CControlJob::tryAggregateComplete(
        const std::function<bool(const std::string&)>& isPjCompletedFn
    ) {
        if (m_pjIds.empty()) return false;
        for (const auto& pj : m_pjIds) {
            if (!isPjCompletedFn(pj)) return false;
        }
        // æ‰€æœ‰ PJ å·²å®Œæˆ â†’ CJ å®Œæˆ
        return complete();
    }
    // â€”— æ—¶é—´æˆ³ â€”— //
    void CControlJob::markQueued() { m_state = CJState::Queued;    m_tQueued = std::chrono::system_clock::now(); }
    void CControlJob::markStart() { m_tStart = std::chrono::system_clock::now(); }
    void CControlJob::markEnd() { m_tEnd = std::chrono::system_clock::now(); }
    // â€”— å·¥å…· â€”— //
    void CControlJob::clampString(std::string& s, size_t maxLen) {
        if (s.size() > maxLen) s.resize(maxLen);
    }
    bool CControlJob::asciiPrintable(const std::string& s) {
        return std::all_of(s.begin(), s.end(), [](unsigned char c) {
            return c >= 0x20 && c <= 0x7E;
            });
    }
    void CControlJob::serialize(std::ostream& os) const {
        write_pod(os, CJ_MAGIC);
        write_pod(os, CJ_VERSION);
        // æ ‡è¯†/优先级/状态/失败原因
        write_string(os, id());                             // æˆ– m_cjId
        write_pod<uint8_t>(os, priority());                 // æˆ– m_priority
        write_pod<uint8_t>(os, static_cast<uint8_t>(state())); // æˆ– m_state
        write_string(os, failReason());                     // æˆ– m_failReason
        // æ—¶é—´æˆ³
        write_opt_time(os, tQueued());
        write_opt_time(os, tStart());
        write_opt_time(os, tEnd());
        // å…³è” PJ åˆ—表
        write_vec_str(os, pjIds());                         // æˆ– m_pjIds
    }
    bool CControlJob::deserialize(std::istream& is, CControlJob& out, std::string* err) {
        auto fail = [&](const char* msg) { if (err) *err = msg; return false; };
        uint32_t magic = 0; if (!read_pod(is, magic)) return fail("read CJ magic");
        if (magic != CJ_MAGIC) return fail("bad CJ magic");
        uint16_t ver = 0; if (!read_pod(is, ver)) return fail("read CJ version");
        if (ver != CJ_VERSION) return fail("unsupported CJ version");
        std::string cjId;
        if (!read_string(is, cjId)) return fail("read CJID");
        uint8_t prio = 0;
        if (!read_pod(is, prio)) return fail("read Priority");
        uint8_t st = 0;
        if (!read_pod(is, st))   return fail("read State");
        std::string failText;
        if (!read_string(is, failText)) return fail("read failReason");
        std::optional<std::chrono::system_clock::time_point> tQ, tS, tE;
        if (!read_opt_time(is, tQ)) return fail("read tQueued");
        if (!read_opt_time(is, tS)) return fail("read tStart");
        if (!read_opt_time(is, tE)) return fail("read tEnd");
        std::vector<std::string> pjIds;
        if (!read_vec_str(is, pjIds)) return fail("read PJIDs");
        // â€”— å†™å›žå¯¹è±¡ï¼ˆç›´æŽ¥æ”¹æˆå‘˜ï¼Œæˆ–通过 setter)——
        // è‹¥ä½ æœ‰ setter:out.setId(...)/setPriority(...)/setState(...)/setFailReason(...)
        out = CControlJob(cjId);
        out.setPriority(prio);
        // ç›´æŽ¥æ¢å¤å†…部状态(若你要求走状态机,可在这里按合法过渡调用 queue()/start()/...)
        // ç®€åŒ–:直接赋值(你在 CControlJob.cpp å†…部,可访问私有成员)
        struct Access : CControlJob {
            using CControlJob::m_state;
            using CControlJob::m_failReason;
            using CControlJob::m_tQueued;
            using CControlJob::m_tStart;
            using CControlJob::m_tEnd;
            using CControlJob::m_pjIds;
        };
        auto& a = reinterpret_cast<Access&>(out);
        a.m_state = static_cast<CJState>(st);
        a.m_failReason = std::move(failText);
        a.m_tQueued = std::move(tQ);
        a.m_tStart = std::move(tS);
        a.m_tEnd = std::move(tE);
        a.m_pjIds = std::move(pjIds);
        return true;
    }
    std::string CControlJob::getStateText()
    {
        switch (m_state)
        {
        case SERVO::CJState::NoState:
            return "NoState";
            break;
        case SERVO::CJState::Queued:
            return "Queued";
            break;
        case SERVO::CJState::Executing:
            return "Executing";
            break;
        case SERVO::CJState::Paused:
            return "Paused";
            break;
        case SERVO::CJState::Completed:
            return "Completed";
            break;
        case SERVO::CJState::Aborted:
            return "Aborted";
            break;
        case SERVO::CJState::Failed:
            return "Failed";
            break;
        default:
            break;
        }
        return "";
    }
}
SourceCode/Bond/Servo/CControlJob.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
#pragma once
#include <string>
#include <vector>
#include <algorithm>
#include <cstdint>
#include <optional>
#include <chrono>
#include <functional>
#include "ProcessJob.h"
// â€”— æ ‡å‡†/项目内约定的属性名(A:40)——
// è¯´æ˜Žï¼šä¸ä»¥ç©ºæ ¼å¼€å¤´/结尾,字符范围 0x20-0x7E,且不能包含 > : ? * ~
inline constexpr const char* CJ_ATTR_CJID = "CJID";       // A:n
inline constexpr const char* CJ_ATTR_PRIORITY = "Priority";   // U1
inline constexpr const char* CJ_ATTR_PRJOBLIST = "PRJOBLIST";  // L:n of A:n (PJID[])
namespace SERVO {
    /// CJ çŠ¶æ€ï¼ˆè´´è¿‘ E40 è¯­ä¹‰ï¼‰
    enum class CJState : uint8_t {
        NoState = 0,
        Queued,
        Executing,
        Paused,
        Completed,
        Aborted,
        Failed
    };
    /// åˆ›å»º/修改结果中的错误项
    struct CJError {
        uint16_t    code;   // è‡ªå®šä¹‰é”™è¯¯ç ï¼ˆä¾‹ï¼š2001=PJ_NOT_FOUND, 2002=NOT_JOINABLE)
        std::string text;   // å»ºè®®åŒ…含定位信息(ATTRID/PJID)
    };
    /// CControlJob:Control Job ç®¡ç†ç±»
    class CControlJob {
    public:
        CControlJob();
        explicit CControlJob(std::string cjId);
        explicit CControlJob(CControlJob& src);
        // â€”— åŸºæœ¬å±žæ€§ â€”— //
        const std::string& id()     const noexcept { return m_cjId; }
        CJState            state()  const noexcept { return m_state; }
        uint8_t            priority() const noexcept { return m_priority; }
        void               setPriority(uint8_t p) noexcept { m_priority = p; }
        std::string getStateText();
        // â€”— PJ åˆ—表维护(去重)—— //
        bool addPJ(const std::string& pjId);                // å·²å­˜åœ¨åˆ™ä¸é‡å¤æ·»åŠ 
        bool addPJs(const std::vector<std::string>& ids);   // è¿”回是否有新增
        bool removePJ(const std::string& pjId);             // å­˜åœ¨åˆ™ç§»é™¤
        bool containsPJ(const std::string& pjId) const;
        const std::vector<std::string>& pjIds() const noexcept { return m_pjIds; }
        bool setPJs(const std::vector<CProcessJob*>& pjs);
        void clearPJs() { m_pjIds.clear(); }
        const std::vector<CProcessJob*>& getPjs() { return m_pjs; };
        // â€”— æ ¡éªŒ â€”— //
        struct ValidationIssue { uint32_t code; std::string text; };
        // æ ¡éªŒ CJ æ˜¯å¦å¯åˆ›å»º/更新(例如:PJ æ˜¯å¦å­˜åœ¨ã€æ˜¯å¦å¯åŠ å…¥ï¼‰
        // getPjExistsFn(pjId)->bool:PJ æ˜¯å¦å­˜åœ¨
        // canJoinFn(pjId)->bool     ï¼šPJ å½“前是否允许加入 CJ(如 PJ çŠ¶æ€ä¸º Queued ç­‰ï¼‰
        bool validateForCreate(
            const std::function<bool(uint32_t& code, std::string& msg)>& canCreateCjFn,
            const std::function<bool(const std::string&)>& getPjExistsFn,
            const std::function<bool(const std::string&)>& canJoinFn
        );
        const std::vector<CControlJob::ValidationIssue>& CControlJob::issues();
        // â€”— S14F9 â†’ S14F10 çš„“应用结果”模型 â€”— //
        struct CreateRequest {
            std::optional<uint8_t>           priority;      // è‹¥æœ‰åˆ™è¦†ç›–
            std::vector<std::string>         requestedPjIds;// æƒ³è¦ç»‘定的 PJ åˆ—表
        };
        struct CreateResult {
            std::vector<std::string> acceptedPjIds; // æˆåŠŸç»‘å®šçš„ PJ(用于回显 PRJOBLIST)
            std::vector<CJError>     errors;        // å¤±è´¥é¡¹ï¼ˆå« PJID è¯´æ˜Žï¼‰
            uint8_t                  objack{ 0 };     // 0=全成功, 1=部分成功, 2=全失败
        };
        // åº”用创建/更新请求:只绑定允许的 PJ,并生成 OBJACK/错误清单
        // getPjExistsFn / canJoinFn åŒä¸Š
        CreateResult applyCreate(
            const CreateRequest& req,
            const std::function<bool(const std::string&)>& getPjExistsFn,
            const std::function<bool(const std::string&)>& canJoinFn
        );
        // â€”— çŠ¶æ€æœº â€”— //
        bool queue();          // NoState -> Queued
        bool start();          // Queued  -> Executing
        bool pause();          // Executing -> Paused
        bool resume();         // Paused -> Executing
        bool complete();       // Executing/Paused -> Completed
        bool abort();          // éžç»ˆæ€ -> Aborted
        bool fail(std::string reason); // ä»»æ„ -> Failed
        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; }
        // â€”— æ±‡æ€»çŠ¶æ€è¾…åŠ©ï¼ˆå¯é€‰ï¼‰â€”â€” //
        // æ ¹æ®å¤–部提供的“PJ æ˜¯å¦å·²å®Œæˆâ€åˆ¤æ–­ï¼Œå°è¯•把 CJ èšåˆç½®ä¸º Completed。
        // isPjCompletedFn(pjId)->bool
        bool tryAggregateComplete(const std::function<bool(const std::string&)>& isPjCompletedFn);
        // å·¥å…·ï¼šç»Ÿä¸€å­—符串限制
        static void clampString(std::string& s, size_t maxLen);
        static bool asciiPrintable(const std::string& s);
        static constexpr uint32_t CJ_MAGIC = 0x434A5031; // "CJP1"
        static constexpr uint16_t CJ_VERSION = 0x0001;
        void serialize(std::ostream& os) const;
        static bool deserialize(std::istream& is, CControlJob& out, std::string* err = nullptr);
    private:
        void markQueued();
        void markStart();
        void markEnd();
    protected:
        // â€”— æ ‡è¯† & é…ç½® â€”— //
        std::string m_cjId;
        uint8_t     m_priority{ 5 }; // ç¼ºçœä¼˜å…ˆçº§ï¼ˆè‡ªå®šï¼‰
        // â€”— ç»„成 â€”— //
        std::vector<std::string> m_pjIds;
        std::vector<CProcessJob*> m_pjs;
        // â€”— çŠ¶æ€ / æ–‡æœ¬ â€”— //
        CJState     m_state{ CJState::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; // CJID / PJID ç­‰
        // é”™è¯¯åˆ—表
        std::vector<ValidationIssue> m_issues;
    };
}
SourceCode/Bond/Servo/CControlJobDlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
// CControlJobDlg.cpp: å®žçŽ°æ–‡ä»¶
//
#include "stdafx.h"
#include "Servo.h"
#include "CControlJobDlg.h"
#include "afxdialogex.h"
// CControlJobDlg å¯¹è¯æ¡†
IMPLEMENT_DYNAMIC(CControlJobDlg, CDialogEx)
CControlJobDlg::CControlJobDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
{
    m_pControlJob = nullptr;
}
CControlJobDlg::~CControlJobDlg()
{
}
void CControlJobDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_listCtrl);
}
BEGIN_MESSAGE_MAP(CControlJobDlg, CDialogEx)
    ON_WM_SIZE()
END_MESSAGE_MAP()
void CControlJobDlg::SetControlJob(SERVO::CControlJob* pControlJob)
{
    m_pControlJob = pControlJob;
}
// CControlJobDlg æ¶ˆæ¯å¤„理程序
BOOL CControlJobDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // label字体
    LOGFONT lf{};
    GetFont()->GetLogFont(&lf);
    lf.lfHeight = -20;
    lf.lfWeight = FW_BOLD;
    _tcscpy_s(lf.lfFaceName, _T("Arial"));
    m_fontNoJob.CreateFontIndirect(&lf);
    GetDlgItem(IDC_LABEL_NO_JOB)->SetFont(&m_fontNoJob);
    // åˆ—表控件
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
    m_listCtrl.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS);
    m_listCtrl.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 180);
    m_listCtrl.InsertColumn(1, _T("类型"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(2, _T("状态"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(3, _T("配方"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(4, _T("Port / Carrier / Slot"), LVCFMT_LEFT, 180);
    m_listCtrl.InsertColumn(5, _T("描述"), LVCFMT_LEFT, 220);
    // æŽ§ä»¶çŠ¶æ€
    Resize();
    ShowGroup1(m_pControlJob == nullptr);
    ShowGroup2(m_pControlJob != nullptr);
    LoadData();
    return TRUE;  // return TRUE unless you set the focus to a control
                  // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
}
void CControlJobDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    if (GetDlgItem(IDC_LIST1) == nullptr) return;
    Resize();
}
void CControlJobDlg::Resize()
{
    CRect rcClient, rcItem;
    CWnd* pItem;
    GetClientRect(rcClient);
    // å…³é—­æŒ‰é’®
    int y = rcClient.bottom - 12;
    pItem = GetDlgItem(IDCANCEL);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(rcClient.right - 12 - rcItem.Width(),
        y - rcItem.Height(),
        rcItem.Width(), rcItem.Height());
    y -= rcItem.Height();
    y -= 12;
    // çº¿
    pItem = GetDlgItem(IDC_LINE1);
    pItem->MoveWindow(12, y, rcClient.Width() - 24, 2);
    y -= 2;
    // Label
    pItem = GetDlgItem(IDC_LABEL_NO_JOB);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow((rcClient.Width() - rcItem.Width()) / 2,
        (y - rcItem.Height()) / 2,
        rcItem.Width(), rcItem.Height());
    // ListCtrl
    pItem = GetDlgItem(IDC_LIST1);
    pItem->MoveWindow(12, 12, rcClient.Width() - 24, y - 12);
}
void CControlJobDlg::ShowGroup1(BOOL bShow)
{
    GetDlgItem(IDC_LABEL_NO_JOB)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
    GetDlgItem(IDC_LINE1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
}
void CControlJobDlg::ShowGroup2(BOOL bShow)
{
    GetDlgItem(IDC_LIST1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
}
void CControlJobDlg::LoadData()
{
    m_listCtrl.DeleteAllItems();
    if (m_pControlJob != nullptr) {
        auto* root1 = m_listCtrl.InsertRoot({ m_pControlJob->id().c_str(), _T("ControlJob"),
            m_pControlJob->getStateText().c_str(), _T("") });
        auto pjs = m_pControlJob->getPjs();
        for (auto pj : pjs) {
            auto* root2 = m_listCtrl.InsertChild(root1, {pj->id().c_str(),  _T("ProcessJob"),
                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), _T(""), _T("") });
            auto cs = pj->carriers();
            for (auto c : cs) {
                for (auto g : c.contexts) {
                    SERVO::CGlass* pGlass = (SERVO::CGlass*)g;
                    if (pGlass != nullptr) {
                        int port, slot;
                        pGlass->getOrginPort(port, slot);
                        std::string carrier = c.carrierId + " / Port" + std::to_string(port + 1) + " / Slot" + std::to_string(slot + 1);
                        m_listCtrl.InsertChild(root2, { pGlass->getID().c_str(), _T("Glass"),
                            pGlass->getStateText().c_str(), _T(""), carrier.c_str(), _T("") });
                    }
                    else {
                        m_listCtrl.InsertChild(root2, { "Null", _T("Glass"), _T(""), _T(""), c.carrierId.c_str(), _T("") });
                    }
                }
            }
            root2->expanded = true;
        }
        root1->expanded = true;
        m_listCtrl.RebuildVisible();
    }
}
SourceCode/Bond/Servo/CControlJobDlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
#pragma once
#include "CExpandableListCtrl.h"
#include "CControlJob.h"
// CControlJobDlg å¯¹è¯æ¡†
class CControlJobDlg : public CDialogEx
{
    DECLARE_DYNAMIC(CControlJobDlg)
public:
    CControlJobDlg(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CControlJobDlg();
public:
    void SetControlJob(SERVO::CControlJob* pControlJob);
private:
    void Resize();
    void ShowGroup1(BOOL bShow);
    void ShowGroup2(BOOL bShow);
    void LoadData();
private:
    SERVO::CControlJob* m_pControlJob;
    CFont m_fontNoJob;
protected:
    CExpandableListCtrl m_listCtrl;
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_CONTROL_JOB };
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV æ”¯æŒ
    DECLARE_MESSAGE_MAP()
public:
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
};
SourceCode/Bond/Servo/CExpandableListCtrl.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,313 @@
#include "stdafx.h"
#include "CExpandableListCtrl.h"
IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl)
CExpandableListCtrl::CExpandableListCtrl() {}
CExpandableListCtrl::~CExpandableListCtrl() {}
BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl)
    ON_WM_CREATE()
    ON_NOTIFY_REFLECT(NM_CLICK, &CExpandableListCtrl::OnClick)
    ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CExpandableListCtrl::OnCustomDraw)
END_MESSAGE_MAP()
int CExpandableListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CListCtrl::OnCreate(lpCreateStruct) == -1)
        return -1;
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
    return 0;
}
void CExpandableListCtrl::PreSubclassWindow()
{
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
    CListCtrl::PreSubclassWindow();
}
CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
{
    auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
    for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i];
    n->level = 0;
    Node* raw = n.get();
    m_roots.emplace_back(std::move(n));
    return raw;
}
CExpandableListCtrl::Node* CExpandableListCtrl::InsertChild(Node* parent, const std::vector<CString>& cols)
{
    ASSERT(parent);
    auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
    for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i];
    n->parent = parent;
    n->level = parent->level + 1;
    Node* raw = n.get();
    parent->children.emplace_back(std::move(n));
    return raw;
}
void CExpandableListCtrl::appendVisible(Node* n)
{
    m_visible.push_back(n);
    if (n->expanded) {
        for (auto& ch : n->children) {
            appendVisible(ch.get());
        }
    }
}
void CExpandableListCtrl::RebuildVisible()
{
    // 1) é‡å»ºå¯è§åºåˆ—
    m_visible.clear();
    for (auto& r : m_roots) appendVisible(r.get());
    // 2) é‡ç»˜/重填数据
    SetRedraw(FALSE);
    DeleteAllItems();
    // æ’入可见行
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        Node* n = m_visible[i];
        LVITEM lvi{};
        lvi.mask = LVIF_TEXT;
        lvi.iItem = i;
        lvi.iSubItem = 0;
        lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0]));
        InsertItem(&lvi);
        for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) {
            CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T("");
            SetItemText(i, col, txt);
        }
    }
    SetRedraw(TRUE);
    Invalidate();
}
void CExpandableListCtrl::Expand(Node* n)
{
    if (!n || n->children.empty()) return;
    if (!n->expanded) { n->expanded = true; RebuildVisible(); }
}
void CExpandableListCtrl::Collapse(Node* n)
{
    if (!n || n->children.empty()) return;
    if (n->expanded) { n->expanded = false; RebuildVisible(); }
}
void CExpandableListCtrl::Toggle(Node* n)
{
    if (!n || n->children.empty()) return;
    n->expanded = !n->expanded;
    RebuildVisible();
}
CExpandableListCtrl::Node* CExpandableListCtrl::GetNodeByVisibleIndex(int i) const
{
    if (i < 0 || i >= (int)m_visible.size()) return nullptr;
    return m_visible[i];
}
CRect CExpandableListCtrl::expanderRectForRow(int row) const
{
    CRect rcLabel;
    if (!const_cast<CExpandableListCtrl*>(this)->GetSubItemRect(row, 0, LVIR_LABEL, rcLabel))
        return CRect(0, 0, 0, 0);
    Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row);
    if (!n || n->children.empty())
        return CRect(0, 0, 0, 0); // å¶å­ä¸å ä½ï¼Œæ–‡æœ¬å°±ä¸ä¼šè¢«å¤šæŽ¨ä¸€æ ¼
    const int indent = n->level;
    const int left = rcLabel.left + m_expanderPadding + indent * 16;
    return CRect(
        left,
        rcLabel.CenterPoint().y - m_expanderSize / 2,
        left + m_expanderSize,
        rcLabel.CenterPoint().y + m_expanderSize / 2
    );
}
void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    if (pia->iItem >= 0) {
        CPoint pt = pia->ptAction;
        // å‘½ä¸­å±•开按钮?
        CRect expRc = expanderRectForRow(pia->iItem);
        if (expRc.PtInRect(pt)) {
            Node* n = GetNodeByVisibleIndex(pia->iItem);
            if (n && !n->children.empty()) {
                Toggle(n);
            }
        }
    }
    *pResult = 0;
}
void CExpandableListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMLVCUSTOMDRAW pCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
    switch (pCD->nmcd.dwDrawStage)
    {
    case CDDS_PREPAINT:
        *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYSUBITEMDRAW;
        return;
    case CDDS_ITEMPREPAINT:
        *pResult = CDRF_NOTIFYSUBITEMDRAW;
        return;
    case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
    {
        const int row = (int)pCD->nmcd.dwItemSpec;
        const int col = pCD->iSubItem;
        CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
        if (col == 0)
        {
            CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
            Node* n = GetNodeByVisibleIndex(row);
            if (!n) { *pResult = CDRF_DODEFAULT; return; }
            // 1) èƒŒæ™¯/前景颜色:按是否选中
            const bool selected = (GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
            const bool focusOnCtrl = (GetSafeHwnd() == ::GetFocus());
            COLORREF bk = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE)
                : ListView_GetBkColor(m_hWnd);
            COLORREF txt = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)
                : ListView_GetTextColor(m_hWnd);
            // ä»…在需要时填充背景(避免“黑一片”)
            CBrush bkBrush(bk);
            pDC->FillRect(rc, &bkBrush);
            // 2) å±•å¼€/折叠指示(参考旧项目的右对齐坐标法,做像素对齐,纯GDI)
            if (!n->children.empty())
            {
                CRect box = expanderRectForRow(row);
                // ---- å¯è°ƒå‚数:与旧代码命名一致 ----
                // å³ä¾§ç•™ç™½ï¼ˆä¸Žæ–‡æœ¬é—´éš™/网格线保持距离)
                const int ROFFSET = 2;
                // é—­åˆ/展开的“宽度”设置:奇数更顺眼(9/11 éƒ½è¡Œï¼‰
                const int WIDE = max(9, min(min(box.Width(), box.Height()), 13)); // â–¶ çš„边长
                const int WIDE2 = WIDE / 2;                                        // ä¸€åŠ
                const int EXPANDED_WIDE = WIDE;                                           // â–¼ çš„边长
                // è½»å¾®å†…缩,避免贴边(与你旧代码“按钮要刷一下”同效)
                box.DeflateRect(1, 1);
                // ç»Ÿä¸€åšå¶æ•°å¯¹é½ï¼Œå‡å°‘半像素锯齿
                auto even = [](int v) { return (v & 1) ? (v - 1) : v; };
                // è®¡ç®—“自下向上”的基准偏移,与旧 TreeCtrl ä¸€è‡´
                // è¿™é‡Œç”¨ box ä½œä¸º pRect
                POINT pt[3];
                if (n->expanded) {
                    // â–¼
                    int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - EXPANDED_WIDE;
                    pt[0].y = box.bottom - nBottomOffset;
                    pt[1].x = box.right - ROFFSET;
                    pt[1].y = box.bottom - nBottomOffset;
                    pt[2].x = box.right - ROFFSET;
                    pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE;
                }
                else {
                    // â–¶
                    int nBottomOffset = (box.Height() - WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - WIDE2;
                    pt[0].y = box.bottom - nBottomOffset - WIDE;
                    pt[1].x = box.right - ROFFSET - WIDE2;
                    pt[1].y = box.bottom - nBottomOffset;
                    pt[2].x = box.right - ROFFSET;
                    pt[2].y = box.bottom - nBottomOffset - WIDE2;
                }
                // ä»…填充,不描边(描边会加重台阶感);颜色用 txt ä¸Žä¸»é¢˜ä¸€è‡´
                HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN));
                HBRUSH   hBrush = CreateSolidBrush(txt);
                HGDIOBJ oldBrush = pDC->SelectObject(hBrush);
                pDC->Polygon(pt, 3);
                pDC->SelectObject(oldPen);
                pDC->SelectObject(oldBrush);
                DeleteObject(hBrush);
            }
            // 3) æ–‡æœ¬ï¼šåŸºäºŽé¦–列区域右移(区分是否有子节点)
            const int indentPx = n->level * 14;
            const int baseLeft = rc.left + m_expanderPadding + indentPx;
            CRect textRc = rc;
            if (!n->children.empty()) {
                // æœ‰å­é¡¹ï¼šé¢„留按钮位 + æ–‡æœ¬é—´éš™
                textRc.left = baseLeft + m_expanderSize + m_textGap;
            }
            else {
                // å¶å­è¡Œï¼šä¸é¢„留按钮位,只给一点点叶子间隙(让层级缩进仍然生效)
                constexpr int kLeafGap = 2; // ä½ å¯è°ƒ 0~4
                textRc.left = baseLeft + kLeafGap;
            }
            pDC->SetBkMode(TRANSPARENT);
            pDC->SetTextColor(txt);
            CString txt0 = n->cols.empty() ? _T("") : n->cols[0];
            pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
            // â€”— ç”»å®Œä¸‰è§’与文本之后,补一条该行的底部横向网格线 â€”—
            // ä»…当开启了 LVS_EX_GRIDLINES æ‰ç»˜åˆ¶
            if (GetExtendedStyle() & LVS_EX_GRIDLINES)
            {
                // ç”¨æ•´è¡Œ bounds,保证横线贯穿所有列的可见宽度
                CRect rcRow;
                GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
                // åº•è¾¹ y åæ ‡ï¼ˆä¸Žç³»ç»Ÿç½‘格线对齐)
                const int y = rcRow.bottom - 1;
                // é¢œè‰²ä¸Žç³»ç»Ÿé£Žæ ¼æŽ¥è¿‘;若觉得偏浅,可换 COLOR_3DSHADOW
                CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT));
                CPen* oldPen = pDC->SelectObject(&pen);
                // æ¨ªçº¿ä»Žè¡Œå·¦åˆ°è¡Œå³ï¼ˆå½“前可见区域)
                pDC->MoveTo(rcRow.left, y);
                pDC->LineTo(rcRow.right, y);
                pDC->SelectObject(oldPen);
            }
            *pResult = CDRF_SKIPDEFAULT;
            return;
        }
        // å…¶ä»–列默认绘制
        *pResult = CDRF_DODEFAULT;
        return;
    }
    }
    *pResult = CDRF_DODEFAULT;
}
SourceCode/Bond/Servo/CExpandableListCtrl.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
#pragma once
#include <vector>
#include <memory>
class CExpandableListCtrl : public CListCtrl
{
    DECLARE_DYNAMIC(CExpandableListCtrl)
public:
    struct Node {
        Node* parent = nullptr;
        std::vector<std::unique_ptr<Node>> children;
        std::vector<CString> cols; // å„列文本
        bool expanded = false;
        int level = 0; // ç¼©è¿›å±‚级
        Node(int nCols = 1) : cols(nCols) {}
    };
    CExpandableListCtrl();
    virtual ~CExpandableListCtrl();
    // æ•°æ®æž„建
    Node* InsertRoot(const std::vector<CString>& cols);
    Node* InsertChild(Node* parent, const std::vector<CString>& cols);
    // å±•å¼€/折叠
    void Expand(Node* n);
    void Collapse(Node* n);
    void Toggle(Node* n);
    // åˆ·æ–°å¯è§åˆ—表
    void RebuildVisible();
    // ä¾¿æ·ï¼šé€šè¿‡å¯è§è¡Œå·å– Node*
    Node* GetNodeByVisibleIndex(int i) const;
private:
    void appendVisible(Node* n);
    CRect expanderRectForRow(int row) const;        // é¦–列展开按钮区域
    virtual void PreSubclassWindow();
protected:
    // æ¶ˆæ¯
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    DECLARE_MESSAGE_MAP()
private:
    std::vector<std::unique_ptr<Node>> m_roots;     // é¡¶å±‚节点
    std::vector<Node*>                 m_visible;   // å±•开后的可见节点顺序
    int  m_expanderPadding = 6;                     // é¦–列内侧边距
    int  m_expanderSize = 10;                       // å°ä¸‰è§’/方块大小
    int  m_textGap = 6;
};
SourceCode/Bond/Servo/CGlass.cpp
@@ -11,6 +11,7 @@
        m_nOriginPort = 0;
        m_nOriginSlot = 0;
        m_bScheduledForProcessing = FALSE;
        m_pProcessJob = nullptr;
    }
    CGlass::~CGlass()
@@ -89,6 +90,16 @@
    void CGlass::setScheduledForProcessing(BOOL bProcessing)
    {
        m_bScheduledForProcessing = bProcessing;
    }
    CProcessJob* CGlass::getProcessJob()
    {
        return m_pProcessJob;
    }
    void CGlass::setProcessJob(CProcessJob* pProcessJob)
    {
        m_pProcessJob = pProcessJob;
    }
    CPath* CGlass::getPath()
@@ -246,4 +257,116 @@
        return pPath->getInspResult();
    }
    std::string CGlass::getStateText()
    {
        switch (m_state)
        {
        case SERVO::GlsState::NoState:
            return "NoState";
            break;
        case SERVO::GlsState::Queued:
            return "Queued";
            break;
        case SERVO::GlsState::InProcess:
            return "InProcess";
            break;
        case SERVO::GlsState::Paused:
            return "Queued";
            break;
        case SERVO::GlsState::Completed:
            return "Queued";
            break;
        case SERVO::GlsState::Aborted:
            return "Aborted";
            break;
        case SERVO::GlsState::Failed:
            return "Failed";
            break;
        default:
            break;
        }
        return "";
    }
    bool CGlass::queue() {
        if (m_state != GlsState::NoState) return false;
        markQueued();
        return true;
    }
    bool CGlass::start() {
        if (m_state != GlsState::Queued && m_state != GlsState::Paused)
            return false;
        if (!m_tStart.has_value()) markStart();
        m_state = GlsState::InProcess;
        return true;
    }
    bool CGlass::pause() {
        if (m_state != GlsState::InProcess) return false;
        m_state = GlsState::Paused;
        return true;
    }
    bool CGlass::resume() {
        if (m_state != GlsState::Paused) return false;
        m_state = GlsState::InProcess;
        return true;
    }
    bool CGlass::complete() {
        if (m_state != GlsState::InProcess && m_state != GlsState::Paused) return false;
        m_state = GlsState::Completed;
        markEnd();
        return true;
    }
    bool CGlass::abort() {
        if (m_state == GlsState::Completed || m_state == GlsState::Aborted || m_state == GlsState::Failed)
            return false;
        m_state = GlsState::Aborted;
        markEnd();
        return true;
    }
    bool CGlass::fail(std::string reason)
    {
        m_failReason = trimCopy(reason);
        clampString(m_failReason, 128);
        m_state = GlsState::Failed;
        markEnd();
        return true;
    }
    std::string CGlass::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;
    }
    void CGlass::clampString(std::string& s, size_t maxLen)
    {
        if (s.size() > maxLen) s.resize(maxLen);
    }
    // â€”— æ—¶é—´æˆ³ & å·¥å…· â€”—
    void CGlass::markQueued()
    {
        m_state = GlsState::Queued;
        m_tQueued = std::chrono::system_clock::now();
    }
    void CGlass::markStart()
    {
        m_tStart = std::chrono::system_clock::now();
    }
    void CGlass::markEnd()
    {
        m_tEnd = std::chrono::system_clock::now();
    }
}
SourceCode/Bond/Servo/CGlass.h
@@ -7,9 +7,21 @@
#include "CJobDataC.h"
#include "CJobDataS.h"
#include "ServoCommo.h"
#include "ProcessJob.h"
namespace SERVO {
    /// PJ ç”Ÿå‘½å‘¨æœŸï¼ˆè´´è¿‘ E40 å¸¸è§çŠ¶æ€ï¼‰
    enum class GlsState : uint8_t {
        NoState = 0,
        Queued,
        InProcess,
        Paused,
        Completed,
        Aborted,
        Failed
    };
    class CGlass : public CContext
    {
    public:
@@ -28,6 +40,8 @@
        void getOrginPort(int& port, int& slot);
        BOOL isScheduledForProcessing();
        void setScheduledForProcessing(BOOL bProcessing);
        CProcessJob* getProcessJob();
        void setProcessJob(CProcessJob* pProcessJob);
        CPath* getPathWithEq(unsigned int nEqId, unsigned int nUnit);
        CPath* getPath();
        void addPath(unsigned int nEqId, unsigned int nUnit);
@@ -44,6 +58,37 @@
        int setInspResult(unsigned int nEqId, unsigned int nUnit, InspResult result);
        InspResult getInspResult(unsigned int nEqId, unsigned int nUnit);
    public:
        // æ–°å¢žçŠ¶æ€
        GlsState state() const noexcept { return m_state; }
        std::string getStateText();
        GlsState m_state{ GlsState::NoState };
        static void clampString(std::string& s, size_t maxLen);
        static std::string trimCopy(std::string s);
        std::string m_failReason;
        // â€”— çŠ¶æ€æœºï¼ˆå¸¦å®ˆå«ï¼‰â€”â€”
        bool queue();           // NoState -> Queued
        bool start();           // Queued -> InProcess
        bool pause();           // InProcess -> Paused
        bool resume();          // Paused -> InProcess
        bool complete();        // InProcess -> Completed
        bool abort();           // Any (未终态) -> Aborted
        bool fail(std::string reason); // ä»»æ„æ€ -> Failed(记录失败原因)
        // æ—¶é—´æˆ³
        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;
        // æ—¶é—´æˆ³ï¼ˆå¯ç”¨äºŽæŠ¥è¡¨/追溯)
        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; }
        void markQueued();
        void markStart();
        void markEnd();
    private:
        MaterialsType m_type;
        std::string m_strID;
@@ -54,6 +99,7 @@
        int m_nOriginPort;
        int m_nOriginSlot;
        BOOL m_bScheduledForProcessing;            /* æ˜¯å¦å°†åŠ å·¥å¤„ç† */
        CProcessJob* m_pProcessJob;
    };
}
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_PORT2_INUSE);
                CPortStatusReport portStatusReport;
                portStatusReport.setPortStatus(PORT_INUSE);
                portStatusReport.setJobExistenceSlot(0xff );
                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,9 @@
#include "CMaster.h"
#include <future>
#include <vector>
#include "RecipeManager.h"
#include <fstream>
#include "SerializeUtil.h"
namespace SERVO {
@@ -56,12 +59,23 @@
        m_bEnableAlarmReport = true;
        m_bContinuousTransfer = false;
        m_nContinuousTransferCount = 0;
        m_nContinuousTransferStep = CTStep_begin;
        m_nContinuousTransferStep = CTStep_Unknow;
        m_pControlJob = nullptr;
        InitializeCriticalSection(&m_criticalSection);
    }
    CMaster::~CMaster()
    {
        // é‡Šæ”¾Job相关
        for (auto item : m_processJobs) {
            delete item;
        }
        m_processJobs.clear();
        if (m_pControlJob != nullptr) {
            delete m_pControlJob;
            m_pControlJob = nullptr;
        }
        if (m_hEventReadBitsThreadExit[0] != nullptr) {
            ::CloseHandle(m_hEventReadBitsThreadExit[0]);
            m_hEventReadBitsThreadExit[0] = nullptr;
@@ -708,13 +722,15 @@
                // Measurement -> LoadPort
                for (int s = 0; s < 4; s++) {
                    PortType pt = pLoadPorts[s]->getPortType();
                    if (!rmd.armState[0] && pLoadPorts[s]->isEnable()
                    if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_BakeCooling_Measurement)
                        && !rmd.armState[0] && pLoadPorts[s]->isEnable()
                        && (pt == PortType::Unloading || pt == PortType::Both)
                        && pLoadPorts[s]->getPortStatus() == PORT_INUSE) {
                        for (int slot = 0; slot < SLOT_MAX; slot++) {
                            m_pActiveRobotTask = createTransferTask_continuous_transfer(pMeasurement,
                                0, pLoadPorts[s], slot);
                            if (m_pActiveRobotTask != nullptr) {
                                m_nContinuousTransferStep = CTStep_Measurement_LoadPort;
                                m_nContinuousTransferStep = CTStep_end;
                                goto CT_PORT_PUT;
                            }
@@ -727,10 +743,12 @@
                // BakeCooling ->Measurement
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_BakeCooling_BakeCooling3)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBakeCooling,
                        3, pMeasurement, 0);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_BakeCooling_Measurement;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling -> Measurement)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -738,86 +756,104 @@
                
                // BakeCooling内部
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_BakeCooling_BakeCooling2)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBakeCooling,
                        2, pBakeCooling, 3);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_BakeCooling_BakeCooling3;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling-2 -> BakeCooling-3)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_BakeCooling_BakeCooling1)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBakeCooling,
                        1, pBakeCooling, 2);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_BakeCooling_BakeCooling2;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling-1 -> BakeCooling-2)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_VacuumBake_BakeCooling)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBakeCooling,
                        0, pBakeCooling, 1);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_BakeCooling_BakeCooling1;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling-0 -> BakeCooling-1)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                // VacuumBake(G1) -> BakeCooling
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_VacuumBake_VacuumBake)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pVacuumBake,
                        1, pBakeCooling, 0);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_VacuumBake_BakeCooling;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(VacuumBake(G1) -> BakeCooling)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                // VacuumBake(G1) -> VacuumBake(G1)
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_Bonder2_VacuumBake)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pVacuumBake,
                        0, pVacuumBake, 1);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_VacuumBake_VacuumBake;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(VacuumBake(G1-0) -> VacuumBake(G1-1))...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                // Bonder2 -> VacuumBake(G1)
                if (!rmd.armState[0]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_Bonder1_Bonder2)
                    && !rmd.armState[0]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBonder2,
                        1, pVacuumBake, 0);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_Bonder2_VacuumBake;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Bonder2 -> VacuumBake(G1))...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                
                // Bonder1 -> Bonder2
                if (!rmd.armState[0] && !pBonder2->hasBondClass()) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_Fliper_Bonder1)
                    && !rmd.armState[0] && !pBonder2->hasBondClass()) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pBonder1,
                        1, pBonder2, 1);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_Bonder1_Bonder2;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Bonder1 -> Bonder2)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                // Fliper(G2) -> Bonder1
                if (!rmd.armState[0] && !pBonder1->hasBondClass()) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_Aligner_Fliper)
                    &&!rmd.armState[0] && !pBonder1->hasBondClass()) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pFliper,
                        0, pBonder1, 1, 2);
                        0, pBonder1, 1);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_Fliper_Bonder1;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Fliper(G2) -> Bonder1)...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                }
                // Aligner -> Fliper(G2)
                if (!rmd.armState[1]) {
                if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_LoadPort_Aligner)
                    && !rmd.armState[1]) {
                    m_pActiveRobotTask = createTransferTask_continuous_transfer(pAligner,
                        0, pFliper, 0);
                    if (m_pActiveRobotTask != nullptr) {
                        m_nContinuousTransferStep = CTStep_Aligner_Fliper;
                        LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Aligner -> Fliper(G2))...");
                    }
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -826,13 +862,15 @@
                // LoadPort -> Aligner
                for (int s = 0; s < 4; s++) {
                    PortType pt = pLoadPorts[s]->getPortType();
                    if (!rmd.armState[0] && pLoadPorts[s]->isEnable()
                    if ((m_nContinuousTransferStep == CTStep_Unknow || m_nContinuousTransferStep == CTStep_end)
                        && !rmd.armState[0] && pLoadPorts[s]->isEnable()
                        && (pt == PortType::Loading || pt == PortType::Both)
                        && pLoadPorts[s]->getPortStatus() == PORT_INUSE) {
                        for (int slot = 0; slot < SLOT_MAX; slot++) {
                            m_pActiveRobotTask = createTransferTask_continuous_transfer(pLoadPorts[s], 
                                slot, pAligner, 0);
                            if (m_pActiveRobotTask != nullptr) {
                                m_nContinuousTransferStep = CTStep_LoadPort_Aligner;
                                LOGI("<ContinuousTransfer>千传测试,开始搬送任务(LoadPort -> Aligner)...");
                                pEFEM->setContext(m_pActiveRobotTask->getContext());
                                goto CT_PORT_GET;
@@ -1105,6 +1143,25 @@
        };
        listener.onPortStatusChanged = [&](void* pEquipment, short status, __int64 data) {
            LOGE("<Master-%s>onPortStatusChanged。status=%d, data=%lld", ((CEquipment*)pEquipment)->getName().c_str(), status);
            if (status == PORT_INUSE && m_pControlJob != nullptr) {
                CLoadPort* pPort = (CLoadPort*)pEquipment;
                auto pjs = m_pControlJob->getPjs();
                for (auto pj : pjs) {
                    auto carrier = pj->getCarrier(pPort->getCassetteId());
                    if (carrier != nullptr) {
                        for (auto slot : carrier->slots) {
                            CGlass* pGlass = pPort->getGlassFromSlot(slot);
                            carrier->contexts.push_back((void*)pGlass);
                            if (pGlass != nullptr) {
                                pGlass->setProcessJob(pj);
                            }
                        }
                    }
                }
            }
            if (m_listener.onLoadPortStatusChanged != nullptr) {
                m_listener.onLoadPortStatusChanged(this, (CEquipment*)pEquipment, status, data);
            }
@@ -1122,6 +1179,15 @@
    }
    CEquipment* CMaster::getEquipment(int id)
    {
        for (auto item : m_listEquipment) {
            if (item->getID() == id) return item;
        }
        return nullptr;
    }
    CEquipment* CMaster::getEquipment(int id) const
    {
        for (auto item : m_listEquipment) {
            if (item->getID() == id) return item;
@@ -1807,4 +1873,220 @@
    {
        m_nContinuousTransferCount = round;
    }
    int CMaster::setProcessJobs(std::vector<CProcessJob*>& pjs)
    {
        std::vector<SERVO::CProcessJob*> temp;
        for (auto p : pjs) {
            if (p->validate(*this)) {
                p->queue();
                temp.push_back(p);
            }
        }
        m_processJobs = temp;
        this->saveState();
        return (int)m_processJobs.size();
    }
    std::vector<CProcessJob*>& CMaster::getProcessJobs()
    {
        return m_processJobs;
    }
    CProcessJob* CMaster::getProcessJob(const std::string& id)
    {
        for (auto item : m_processJobs) {
            if (item->id().compare(id) == 0) return item;
        }
        return nullptr;
    }
    int CMaster::setControlJob(CControlJob& controlJob)
    {
        // å›žè°ƒï¼šæ˜¯å¦å‚创建ControlJob
        auto canCreateCjFn = [&](uint32_t& cc, std::string& mm) -> bool {
            if (m_pControlJob != nullptr) {
                cc = 1100;
                mm = "当前ControlJob未结批,不能创建新的ControlJob";
                return false;
            }
            return true;
        };
        // å›žè°ƒï¼šæ˜¯å¦å­˜åœ¨
        auto pjExists = [&](const std::string& id) -> bool {
            return getProcessJob(id) != nullptr;
        };
        // å›žè°ƒï¼šæ˜¯å¦å¯åŠ å…¥ CJ(这里定义:必须是 Queued)
        auto pjJoinable = [&](const std::string& id) -> bool {
            auto pj = getProcessJob(id);
            if (pj == nullptr) return false;
            return pj->state() == PJState::Queued;
        };
        bool bRet = controlJob.validateForCreate(canCreateCjFn, pjExists, pjJoinable);
        if (!bRet) return -1;
        std::vector<CProcessJob*> temps;
        m_pControlJob = new CControlJob(controlJob);
        auto pjIds = controlJob.pjIds();
        for (auto id : pjIds) {
            auto pj = getProcessJob(id);
            if (pj != nullptr) {
                temps.push_back(pj);
            }
        }
        m_pControlJob->setPJs(temps);
        this->saveState();
        return 0;
    }
    CControlJob* CMaster::getControlJob()
    {
        return m_pControlJob;
    }
    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;
    }
    bool CMaster::saveState() const
    {
        std::ofstream ofs(m_strStatePath, std::ios::binary);
        if (!ofs) return false;
        // æ–‡ä»¶å¤´
        uint32_t magic = 0x4D415354; // 'MAST'
        uint16_t version = 1;
        ofs.write(reinterpret_cast<const char*>(&magic), sizeof(magic));
        ofs.write(reinterpret_cast<const char*>(&version), sizeof(version));
        // ä¿å­˜ ControlJob
        bool hasCJ = (m_pControlJob != nullptr);
        ofs.write(reinterpret_cast<const char*>(&hasCJ), sizeof(hasCJ));
        if (hasCJ) {
            m_pControlJob->serialize(ofs);
        }
        // ä¿å­˜ ProcessJob åˆ—表
        uint32_t count = static_cast<uint32_t>(m_processJobs.size());
        ofs.write(reinterpret_cast<const char*>(&count), sizeof(count));
        for (const auto& job : m_processJobs) {
            job->serialize(ofs);
        }
        // ä»¥åŽå¯ä»¥åœ¨è¿™é‡Œè¿½åŠ æ–°å­—æ®µ
        return true;
    }
    bool CMaster::loadState(const std::string& path)
    {
        // ä¿å­˜æ–‡ä»¶è·¯å¾„
        m_strStatePath = path;
        std::ifstream ifs(path, std::ios::binary);
        if (!ifs) return false;
        // æ–‡ä»¶å¤´
        uint32_t magic = 0;
        uint16_t version = 0;
        ifs.read(reinterpret_cast<char*>(&magic), sizeof(magic));
        ifs.read(reinterpret_cast<char*>(&version), sizeof(version));
        if (magic != 0x4D415354) {
            // æ–‡ä»¶ä¸åˆæ³•
            return false;
        }
        if (m_pControlJob != nullptr) {
            delete m_pControlJob;
            m_pControlJob = nullptr;
        }
        // è¯»å– ControlJob
        bool hasCJ = false;
        ifs.read(reinterpret_cast<char*>(&hasCJ), sizeof(hasCJ));
        if (hasCJ) {
            m_pControlJob = new CControlJob();
            if (!CControlJob::deserialize(ifs, *m_pControlJob)) return false;
        }
        // è¯»å– ProcessJob åˆ—表
        uint32_t count = 0;
        ifs.read(reinterpret_cast<char*>(&count), sizeof(count));
        m_processJobs.clear();
        for (uint32_t i = 0; i < count; i++) {
            CProcessJob* pProcessJob = new CProcessJob();
            if (!CProcessJob::deserialize(ifs, *pProcessJob)) return false;
            m_processJobs.push_back(pProcessJob);
        }
        // æ‰¾åˆ°CProcessJob指针加入列表中
        std::vector<CProcessJob*> tempPjs;
        auto ids = m_pControlJob->pjIds();
        for (auto id : ids) {
            auto pj = getProcessJob(id);
            if (pj != nullptr) {
                tempPjs.push_back(pj);
            }
        }
        m_pControlJob->setPJs(tempPjs);
        // å¦‚果版本升级,可在这里判断 version æ¥åŠ è½½æ–°å­—æ®µ
        return true;
    }
}
SourceCode/Bond/Servo/CMaster.h
@@ -13,10 +13,25 @@
#include "CArmTray.h"
#include "CCLinkIEControl.h"
#include "CRobotTask.h"
#include "ProcessJob.h"
#include "CControlJob.h"
#define CTStep_begin        0
#define CTStep_end          99
#define CTStep_Unknow                   0
#define CTStep_LoadPort_Aligner         1
#define CTStep_Aligner_Fliper           2
#define CTStep_Fliper_Bonder1           3
#define CTStep_Bonder1_Bonder2          4
#define CTStep_Bonder2_VacuumBake       5
#define CTStep_VacuumBake_VacuumBake    6
#define CTStep_VacuumBake_BakeCooling   7
#define CTStep_BakeCooling_BakeCooling1 8
#define CTStep_BakeCooling_BakeCooling2 9
#define CTStep_BakeCooling_BakeCooling3 10
#define CTStep_BakeCooling_Measurement  11
#define CTStep_Measurement_LoadPort     12
#define CTStep_begin                    CTStep_LoadPort_Aligner
#define CTStep_end                      CTStep_Measurement_LoadPort
namespace SERVO {
    enum class MASTERSTATE {
@@ -50,7 +65,7 @@
        ONCTROUNDEND            onCTRoundEnd;
    } MasterListener;
    class CMaster
    class CMaster : public IResourceView
    {
    public:
        CMaster();
@@ -73,6 +88,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();
@@ -90,6 +106,14 @@
        int carrierRelease(unsigned int port);
        int getContinuousTransferCount();
        void setContinuousTransferCount(int round);
        int setProcessJobs(std::vector<CProcessJob*>& pjs);
        std::vector<CProcessJob*>& getProcessJobs();
        CProcessJob* getProcessJob(const std::string& id);
        int setControlJob(CControlJob& controlJob);
        CControlJob* getControlJob();
        CLoadPort* getPortWithCarrierId(const std::string& carrierId) const;
        bool saveState() const;
        bool loadState(const std::string& path);
    private:
        inline void lock() { EnterCriticalSection(&m_criticalSection); }
@@ -120,6 +144,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;
@@ -165,6 +197,9 @@
    private:
        bool m_bEnableEventReport;
        bool m_bEnableAlarmReport;
        SERVO::CControlJob* m_pControlJob;
        std::vector<SERVO::CProcessJob*> m_processJobs;
        std::string m_strStatePath;
    };
}
SourceCode/Bond/Servo/CVariable.cpp
@@ -43,6 +43,9 @@
        if (_strcmpi("A20", pszFormat) == 0) {
            return SVFromat::A20;
        }
        if (_strcmpi("L", pszFormat) == 0) {
            return SVFromat::L;
        }
        return SVFromat::U1;
    }
@@ -64,6 +67,9 @@
        }
        if (SVFromat::A20 == format) {
            return "A20";
        }
        if (SVFromat::L == format) {
            return "L";
        }
        return "U1";
@@ -113,6 +119,11 @@
        m_strValue = strTemp;
    }
    void CVariable::setValue(std::vector<CVariable>& vars)
    {
        m_varsValue = vars;
    }
    std::string CVariable::getValue()
    {
        std::string strRet;
@@ -125,4 +136,18 @@
        return strRet;
    }
    __int64 CVariable::getIntValue()
    {
        if (m_format == SVFromat::U1 || m_format == SVFromat::U2 || m_format == SVFromat::I2) {
            return m_nValue;
        }
        return 0;
    }
    std::vector<CVariable>& CVariable::getVarsValue()
    {
        return m_varsValue;
    }
}
SourceCode/Bond/Servo/CVariable.h
@@ -9,7 +9,8 @@
        U2,
        I2,
        A20,
        A50
        A50,
        L
    };
    class CVariable
@@ -28,7 +29,10 @@
        std::string& getRemark();
        void setValue(__int64 value);
        void setValue(const char* pszValue);
        void setValue(std::vector<CVariable>& vars);
        std::string getValue();
        __int64 getIntValue();
        std::vector<CVariable>& getVarsValue();
    private:
        unsigned int m_nVarialbeId;
@@ -37,6 +41,7 @@
        std::string m_strRemark;
        __int64 m_nValue;
        std::string m_strValue;
        std::vector<CVariable> m_varsValue;
    };
}
SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -103,6 +103,43 @@
}
void CHsmsPassive::addVariableValueToItem(ISECS2Item* pParent, SERVO::CVariable* pVariable)
{
    ASSERT(pParent);
    ASSERT(pVariable);
    ISECS2Item* pItemList;
    SERVO::SVFromat format = pVariable->getFormat();
    switch (format)
    {
    case SERVO::SVFromat::U1:
        pParent->addU1Item((unsigned char)pVariable->getIntValue(), "SV");
        break;
    case SERVO::SVFromat::U2:
        pParent->addU2Item((unsigned char)pVariable->getIntValue(), "SV");
        break;
    case SERVO::SVFromat::I2:
        pParent->addI2Item((unsigned char)pVariable->getIntValue(), "SV");
        break;
    case SERVO::SVFromat::A20:
    case SERVO::SVFromat::A50:
        pParent->addItem(pVariable->getValue().c_str(), "SV");
        break;
    case SERVO::SVFromat::L:
        pItemList = pParent->addItem();
        {
            auto vars = pVariable->getVarsValue();
            for (auto v : vars) {
                addVariableValueToItem(pItemList, &v);
            }
        }
        break;
    default:
        break;
    }
}
void CHsmsPassive::linkEventReport(unsigned int CEID, unsigned int RPTID)
{
    SERVO::CCollectionEvent* pEvent = getEvent(CEID);
@@ -288,6 +325,14 @@
    auto v = getVariable(pszName);
    if (v != nullptr) {
        v->setValue(value);
    }
}
void CHsmsPassive::setVariableValue(const char* pszName, std::vector<SERVO::CVariable>& vars)
{
    auto v = getVariable(pszName);
    if (v != nullptr) {
        v->setValue(vars);
    }
}
@@ -536,6 +581,9 @@
            // S1F1
            replyAreYouThere(pMessage);
        }
        else if (nStream == 1 && pHeader->function == 3) {
            replySelectedEquipmentStatusData(pMessage);
        }
        else if (nStream == 1 && pHeader->function == 13) {
            replyEstablishCommunications(pMessage);
        }
@@ -583,6 +631,12 @@
        }
        else if (nStream == 10 && pHeader->function == 3) {
            replyTerminalDisplay(pMessage);
        }
        else if (nStream == 14 && pHeader->function == 9) {
            replyCreateObj(pMessage);
        }
        else if (nStream == 16 && pHeader->function == 15) {
            replyPRJobMultiCreate(pMessage);
        }
    };
@@ -898,6 +952,46 @@
    m_pPassive->sendMessage(pMessage);
    LOGI("<HSMS>[SECS Msg SEND]%s", pMessage->toString());
    HSMS_Destroy1Message(pMessage);
    return 0;
}
// S1F3
int CHsmsPassive::replySelectedEquipmentStatusData(IMessage* pRecv)
{
    if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
        return ER_NOTSELECT;
    }
    IMessage* pMessage = NULL;
    HSMS_Create1Message(pMessage, m_nSessionId, 1, 4, pRecv->getHeader()->systemBytes);
    ASSERT(pMessage);
    unsigned char SVU1 = 0;
    unsigned int SVID = 0;
    ISECS2Item* pBody = pRecv->getBody();
    if (pBody == nullptr || pBody->getType() != SITYPE::L) {
        pMessage->getBody()->addU1Item(SVU1, "SV");
        goto MYREPLY;
    }
    if (!pBody->getSubItemU4(0, SVID)) {
        pMessage->getBody()->addU1Item(SVU1, "SV");
        goto MYREPLY;
    }
    SERVO::CVariable* pVariable = getVariable(SVID);
    if (pVariable == nullptr) {
        pMessage->getBody()->addU1Item(SVU1, "SV");
        goto MYREPLY;
    }
    addVariableValueToItem(pMessage->getBody(), pVariable);
MYREPLY:
    m_pPassive->sendMessage(pMessage);
    LOGI("<HSMS>[SECS Msg SEND]%s", pMessage->toString());
    HSMS_Destroy1Message(pMessage);
    return 0;
}
@@ -1447,6 +1541,245 @@
    return 0;
}
// S14F9
int CHsmsPassive::replyCreateObj(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;
    // æ˜¯å¦åˆ›å»ºæˆåŠŸå¹¶å‡†å¤‡å›žå¤æŠ¥æ–‡
    bool bCreateOk = false;
    IMessage* pReply = NULL;
    HSMS_Create1Message(pReply, m_nSessionId, 14, 10, ++m_nSystemByte);
    ASSERT(pReply);
    // è§£é‡Šæ•°æ®ï¼Œå¾—到ControlJob
    ISECS2Item* pItemAttrs, * pItemAttr, *pItemAttrData;
    const char* pszObjSpec, *pszObjType, *pszAttrId, *pszProcessJobId;
    std::string strObjName, strObjId;
    if (!pBody->getSubItemString(0, pszObjSpec)) return ER_PARAM_ERROR;
    if (!pBody->getSubItemString(1, pszObjType)) return ER_PARAM_ERROR;
    pReply->getBody()->addItem(pszObjSpec, "OBJSPEC");
    ISECS2Item* pReplyItemAttrs = pReply->getBody()->addItem();
    ISECS2Item* pReplyItemAcks = pReply->getBody()->addItem();
    ISECS2Item* pReplyItemAck = pReplyItemAcks->addU1Item(0, "OBJACK");
    ISECS2Item* pReplyItemErrs = pReplyItemAcks->addItem();
    // å½“前只处理类各为ControlJob
    if (_strcmpi(pszObjType, "ControlJob") == 0) {
        // ç±»id
        std::regex re("^([^:]+):([^>]+)>");
        std::smatch match;
        std::string strObjSpec(pszObjSpec);
        if (!std::regex_search(strObjSpec, match, re)) {
            ISECS2Item* pItemError = pReplyItemErrs->addItem();
            pItemError->addU4Item(2001, "ERRCODE");
            pItemError->addItem("参数或报文不正确", "ERRTEXT");
            goto MYREPLY;
        }
        if (match[1].compare("ControlJob") != 0) {
            ISECS2Item* pItemError = pReplyItemErrs->addItem();
            pItemError->addU4Item(2001, "ERRCODE");
            pItemError->addItem("不支持的OBJ", "ERRTEXT");
            goto MYREPLY;
        }
        strObjId = match[2];
        // åˆ›å»ºç±»CControlJob
        SERVO::CControlJob controlJob(strObjId);
        // ç±»å±žæ€§
        pItemAttrs = pBody->getSubItem(2);
        if (pItemAttrs == nullptr) return ER_PARAM_ERROR;
        for (int i = 0; i < pItemAttrs->getSubItemSize(); i++) {
            pItemAttr = pItemAttrs->getSubItem(i);
            if (pItemAttr == nullptr) continue;
            if (!pItemAttr->getSubItemString(0, pszAttrId)) continue;
            if (_strcmpi(pszAttrId, CJ_ATTR_PRIORITY) == 0) {
                uint8_t priority;
                if (pItemAttr->getSubItemU1(1, priority)) {
                    controlJob.setPriority(priority);
                }
            }
            else if (_strcmpi(pszAttrId, CJ_ATTR_PRJOBLIST) == 0) {
                pItemAttrData = pItemAttr->getSubItem(1);
                if (pItemAttrData != nullptr && pItemAttrData->getType() == SITYPE::L) {
                    for (int i = 0; i < pItemAttrData->getSubItemSize(); i++) {
                        if (pItemAttrData->getSubItemString(i, pszProcessJobId)) {
                            std::string strProcessJobId(pszProcessJobId);
                            controlJob.addPJ(strProcessJobId);
                        }
                    }
                }
            }
        }
        ASSERT(m_listener.onControlJobCreate != nullptr);
        int nRet = m_listener.onControlJobCreate(this, controlJob);
        bCreateOk = nRet == 0;
        // æ·»åŠ æ–°å»ºç±»çš„å„ç§å±žæ€§åˆ°å›žå¤æŠ¥æ–‡ä¸­
        if(bCreateOk) {
            {
                ISECS2Item* pReplyItemAttr = pReplyItemAttrs->addItem();
                pReplyItemAttr->addItem(CJ_ATTR_PRIORITY, "ATTRID");
                pReplyItemAttr->addU1Item(controlJob.priority(), "ATTRDATA");
            }
            {
                ISECS2Item* pReplyItemAttr = pReplyItemAttrs->addItem();
                pReplyItemAttr->addItem(CJ_ATTR_PRJOBLIST, "ATTRID");
                ISECS2Item* pItemPjs = pReplyItemAttr->addItem();
                auto pjIds = controlJob.pjIds();
                for (auto id : pjIds) {
                    pItemPjs->addItem(id.c_str(), "PRJOBID");
                }
            }
        }
        else {
            auto issues = controlJob.issues();
            for (auto i : issues) {
                ISECS2Item* pItemError = pReplyItemErrs->addItem();
                pItemError->addU4Item(i.code, "ERRCODE");
                pItemError->addItem(i.text.c_str(), "ERRTEXT");
            }
        }
    }
    else {
        ISECS2Item* pItemError = pReplyItemErrs->addItem();
        pItemError->addU4Item(2001, "ERRCODE");
        pItemError->addItem("不支持的OBJ", "ERRTEXT");
    }
    // å®Œå–„报文并回复
MYREPLY:
    pReplyItemAck->setU1(bCreateOk ? 0 : 1, "OBJACK");
    m_pPassive->sendMessage(pReply);
    LOGI("<HSMS>[SECS Msg SEND]S14F10 (SysByte=%u)", pReply->getHeader()->systemBytes);
    HSMS_Destroy1Message(pReply);
    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->issues().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->issues().empty()) {
                ISECS2Item* pItemErr = pItemErrors2->addItem();
                pItemErr->addU4Item(p->issues()[0].code, "ERRCODE");
                pItemErr->addItem(("<" + p->id() + ">" + p->issues()[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->issues().empty()) delete p;
    }
    pjs.clear();
    return 0;
}
// S5F1
int CHsmsPassive::requestAlarmReport(int ALCD, int ALID, const char* ALTX)
{
@@ -1504,9 +1837,9 @@
    pItemList2->addU4Item(pReport->getReportId(), "RPTID");
    ISECS2Item* pItemList3 = pItemList2->addItem();
    auto values = pReport->getVariables();
    for (auto item : values) {
        pItemList3->addItem(item->getValue().c_str(), "V");
    auto vars = pReport->getVariables();
    for (auto var : vars) {
        addVariableValueToItem(pItemList3, var);
    }
    pAction->setSendMessage(pMessage);
    if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
@@ -1541,6 +1874,11 @@
    return requestEventReportSend("CarrierID_Readed");
}
int CHsmsPassive::requestEventReportSend_PJ_Queued()
{
    return requestEventReportSend("PJ_Queued");
}
SourceCode/Bond/Servo/HsmsPassive.h
@@ -7,6 +7,8 @@
#include <map>
#include <set>
#include "CCollectionEvent.h"
#include "ProcessJob.h"
#include "CControlJob.h"
#define EQCONSTANT_VALUE_MAX    64
@@ -86,6 +88,8 @@
    const char* pszCarrierId,
    unsigned char PTN, 
    std::string& strErrorTxt)> CARRIERACTION;
typedef std::function<int(void* pFrom, std::vector<SERVO::CProcessJob*>& pjs)> PRJOBMULTICREATE;
typedef std::function<int(void* pFrom, SERVO::CControlJob& controlJob)> CONTROLJOBCREATE;
typedef struct _SECSListener
{
    SECSEQOFFLINE                onEQOffLine;
@@ -98,6 +102,8 @@
    EDALARMREPORT                onEnableDisableAlarmReport;
    QUERYPPIDLIST                onQueryPPIDList;
    CARRIERACTION                onCarrierAction;
    PRJOBMULTICREATE            onPRJobMultiCreate;
    CONTROLJOBCREATE            onControlJobCreate;
} SECSListener;
@@ -114,6 +120,9 @@
    /* è®¾ç½®è½¯ä»¶ç‰ˆæœ¬å· æœ€å¤§é•¿åº¦ 20 bytes */
    void setSoftRev(const char* pszRev);
    /* æ·»åŠ å˜é‡å€¼åˆ°ISECS2Item */
    void addVariableValueToItem(ISECS2Item* pParent, SERVO::CVariable* pVariable);
    // è¿žæŽ¥Report
    void linkEventReport(unsigned int CEID, unsigned int RPTID);
@@ -141,6 +150,7 @@
    // è®¾ç½®å˜é‡å€¼
    void setVariableValue(const char* pszName, __int64 value);
    void setVariableValue(const char* pszName, const char* value);
    void setVariableValue(const char* pszName, std::vector<SERVO::CVariable>& vars);
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CReport列表
    int loadReports(const char* pszFilepath);
@@ -181,6 +191,7 @@
    int requestEventReportSend(unsigned int CEID);
    int requestEventReportSend(const char* pszEventName);
    int requestEventReportSend_CarrierID_Readed();
    int requestEventReportSend_PJ_Queued();
private:
    void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
@@ -188,6 +199,7 @@
    /* reply开头的函数为回复函数 */
    int replyAreYouThere(IMessage* pRecv);
    int replyEstablishCommunications(IMessage* pRecv);
    int replySelectedEquipmentStatusData(IMessage* pRecv);
    int replyOnLine(IMessage* pRecv);
    int replyOffLine(IMessage* pRecv);
    int replyEquipmentConstantRequest(IMessage* pRecv);
@@ -203,6 +215,8 @@
    int replyPurgeSpooledData(IMessage* pRecv);
    int replyQueryPPIDList(IMessage* pRecv);
    int replyTerminalDisplay(IMessage* pRecv);
    int replyCreateObj(IMessage* pRecv);
    int replyPRJobMultiCreate(IMessage* pRecv);
private:
    inline void Lock() { EnterCriticalSection(&m_criticalSection); }
SourceCode/Bond/Servo/Model.cpp
@@ -88,7 +88,7 @@
    ::CreateDirectory(strLogDir, NULL);
    CLog::GetLog()->SetOnLogCallback([&](int level, const char* pszMessage) -> void {
        notifyTextAndInt(RX_CODE_LOG, pszMessage, level);
    });
        });
    CLog::GetLog()->SetAutoAppendTimeString(TRUE);
    CLog::GetLog()->SetOutputTarget(OT_FILE);
    CLog::GetLog()->SetLogsDir(strLogDir);
@@ -112,7 +112,7 @@
    listener.onEQConstantRequest = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
        // åœ¨æ­¤å¡«å……常量值,目前仅是加1后返回
        for (auto& item : eqcs) {
            sprintf_s(item.szValue, 256, "Test%d", item.id+1);
            sprintf_s(item.szValue, 256, "Test%d", item.id + 1);
        }
    };
    listener.onEQConstantSend = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
@@ -151,8 +151,8 @@
        }
        return ppids;
    };
    listener.onCarrierAction = [&](void* pFrom,
        unsigned int DATAID,
    listener.onCarrierAction = [&](void* pFrom,
        unsigned int DATAID,
        const char* pszCarrierAction,
        const char* pszCarrierId,
        unsigned char PTN,
@@ -174,6 +174,28 @@
            strErrorTxt = "rejected - invalid state";
            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());
        }
        int nRet = m_master.setProcessJobs(pjs);
        auto processJobs = m_master.getProcessJobs();
        std::vector<SERVO::CVariable> vars;
        for (auto pj : processJobs) {
            SERVO::CVariable var("", "PRJOBID", "A50", "PRJOBID");
            var.setValue(pj->id().c_str());
            vars.push_back(var);
        }
        m_hsmsPassive.setVariableValue("PJQueued", vars);
        m_hsmsPassive.requestEventReportSend_PJ_Queued();
        return nRet;
    };
    listener.onControlJobCreate = [&](void* pFrom, SERVO::CControlJob& controlJob) -> int {
        LOGI("<Model>onControlJobCreate %s %d", controlJob.id().c_str(), controlJob.priority());
        int nRet = m_master.setControlJob(controlJob);
        return nRet;
    };
    m_hsmsPassive.setListener(listener);
    m_hsmsPassive.setEquipmentModelType((LPTSTR)(LPCTSTR)strModeType);
@@ -375,6 +397,14 @@
    m_master.setCacheFilepath((LPTSTR)(LPCTSTR)strMasterDataFile);
    m_master.setCompareMapsBeforeProceeding(m_configuration.isCompareMapsBeforeProceeding());
    // åŠ æˆªJob
    strMasterDataFile.Format(_T("%s\\MasterState.dat"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    std::string strPath = std::string((LPTSTR)(LPCTSTR)strMasterDataFile);
    if (!m_master.loadState(strPath)) {
        LOGE("<Master>加载MasterState.dat文件失败.");
    }
    // åŠ è½½è­¦å‘Šä¿¡æ¯
    AlarmManager& alarmManager = AlarmManager::getInstance();
    char szBuffer[MAX_PATH];
SourceCode/Bond/Servo/ProcessJob.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,433 @@
#include "stdafx.h"
#include "ProcessJob.h"
#include <cctype>
#include <fstream>
#include "SerializeUtil.h"
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()
    {
    }
    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::issues()
    {
        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));
        }
    }
    // --------- æ ¸å¿ƒï¼šserialize/deserialize ---------
    void CProcessJob::serialize(std::ostream& os) const {
        // Í·
        write_pod(os, PJ_FILE_MAGIC);
        write_pod(os, PJ_FILE_VERSION);
        // åŸºæœ¬
        write_string(os, m_pjId);
        write_string(os, m_parentCjId);
        // é…æ–¹
        uint8_t recipeType = static_cast<uint8_t>(m_recipeMethod);
        write_pod(os, m_recipeMethod);
        write_string(os, m_recipeSpec);
        // ç‰©æ–™ï¼ˆå¤š Carrier & Slot)
        {
            uint32_t n = static_cast<uint32_t>(m_carriers.size());
            write_pod(os, n);
            for (const auto& cs : m_carriers) {
                write_string(os, cs.carrierId);
                write_vec<uint8_t>(os, cs.slots);
            }
        }
        // å‚æ•°
        {
            uint32_t n = static_cast<uint32_t>(m_params.size());
            write_pod(os, n);
            for (const auto& p : m_params) {
                write_string(os, p.name);
                write_string(os, p.value);
            }
        }
        // æš‚停事件
        write_vec<uint32_t>(os, m_pauseEvents);
        // å¯åŠ¨ç­–ç•¥ & çŠ¶æ€
        uint8_t startPolicy = static_cast<uint8_t>(m_startPolicy);
        uint8_t st = static_cast<uint8_t>(m_state);
        write_pod(os, startPolicy);
        write_pod(os, st);
        // å¤±è´¥åŽŸå› 
        write_string(os, m_failReason);
        // æ—¶é—´æˆ³
        write_opt_time(os, m_tQueued);
        write_opt_time(os, m_tStart);
        write_opt_time(os, m_tEnd);
    }
    bool CProcessJob::deserialize(std::istream& is, CProcessJob& out, std::string* err) {
        auto fail = [&](const char* msg) { if (err) *err = msg; return false; };
        uint32_t magic = 0; if (!read_pod(is, magic)) return fail("read magic failed");
        if (magic != PJ_FILE_MAGIC) return fail("bad magic");
        uint16_t ver = 0; if (!read_pod(is, ver)) return fail("read version failed");
        if (ver != PJ_FILE_VERSION) return fail("unsupported version");
        // åŸºæœ¬
        if (!read_string(is, out.m_pjId))        return fail("read pjId");
        if (!read_string(is, out.m_parentCjId))  return fail("read parentCjId");
        // é…æ–¹
        uint8_t recipeType = 0; if (!read_pod(is, recipeType)) return fail("read recipeType");
        out.m_recipeMethod = static_cast<RecipeMethod>(recipeType);
        if (!read_string(is, out.m_recipeSpec)) return fail("read recipeSpec");
        // ç‰©æ–™
        {
            uint32_t n = 0; if (!read_pod(is, n)) return fail("read carriers count");
            out.m_carriers.clear(); out.m_carriers.reserve(n);
            for (uint32_t i = 0; i < n; ++i) {
                CarrierSlotInfo cs;
                if (!read_string(is, cs.carrierId)) return fail("read carrierId");
                if (!read_vec<uint8_t>(is, cs.slots)) return fail("read slots");
                out.m_carriers.emplace_back(std::move(cs));
            }
        }
        // å‚æ•°
        {
            uint32_t n = 0; if (!read_pod(is, n)) return fail("read params count");
            out.m_params.clear(); out.m_params.reserve(n);
            for (uint32_t i = 0; i < n; ++i) {
                PJParam p;
                if (!read_string(is, p.name))  return fail("read param name");
                if (!read_string(is, p.value)) return fail("read param value");
                out.m_params.emplace_back(std::move(p));
            }
        }
        // æš‚停事件
        if (!read_vec<uint32_t>(is, out.m_pauseEvents)) return fail("read pauseEvents");
        // å¯åŠ¨ç­–ç•¥ & çŠ¶æ€
        uint8_t startPolicy = 0, st = 0;
        if (!read_pod(is, startPolicy)) return fail("read startPolicy");
        if (!read_pod(is, st))          return fail("read state");
        out.m_startPolicy = static_cast<StartPolicy>(startPolicy);
        out.m_state = static_cast<PJState>(st);
        // å¤±è´¥åŽŸå› 
        if (!read_string(is, out.m_failReason)) return fail("read failReason");
        // æ—¶é—´æˆ³
        if (!read_opt_time(is, out.m_tQueued)) return fail("read tQueued");
        if (!read_opt_time(is, out.m_tStart))  return fail("read tStart");
        if (!read_opt_time(is, out.m_tEnd))    return fail("read tEnd");
        return true;
    }
    std::string CProcessJob::getStateText()
    {
        switch (m_state)
        {
        case SERVO::PJState::NoState:
            return "NoState";
            break;
        case SERVO::PJState::Queued:
            return "Queued";
            break;
        case SERVO::PJState::SettingUp:
            return "SettingUp";
            break;
        case SERVO::PJState::InProcess:
            return "InProcess";
            break;
        case SERVO::PJState::Paused:
            return "Queued";
            break;
        case SERVO::PJState::Aborting:
            return "Aborting";
            break;
        case SERVO::PJState::Completed:
            return "Queued";
            break;
        case SERVO::PJState::Aborted:
            return "Aborted";
            break;
        case SERVO::PJState::Failed:
            return "Failed";
            break;
        default:
            break;
        }
        return "";
    }
    CarrierSlotInfo* CProcessJob::getCarrier(std::string& strId)
    {
        for (auto& item : m_carriers) {
            if (item.carrierId.compare(strId) == 0) {
                return &item;
            }
        }
        return nullptr;
    }
}
SourceCode/Bond/Servo/ProcessJob.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,216 @@
#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[]
        std::vector<void*> contexts;    // Glass
    };
    /// ç®€å•资源视图接口:供 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:
        // â€”— æž„造 / åŸºæœ¬è®¾ç½® â€”—
        CProcessJob();
        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
        std::string getStateText();
        // ç»‘定父 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>& issues();
        // â€”— çŠ¶æ€æœºï¼ˆå¸¦å®ˆå«ï¼‰â€”â€”
        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; }
        CarrierSlotInfo* getCarrier(std::string& strId);
        // åˆ¤å®šæ˜¯å¦â€œæŒ‰è½½å…·/卡位”方式
        bool usesCarrierSlots() const noexcept { return !m_carriers.empty(); }
    public:
        // ====== ç‰ˆæœ¬å¤´å¸¸é‡ï¼ˆå»ºè®®ä¿ç•™ï¼Œä¾¿äºŽå…¼å®¹ï¼‰======
        static constexpr uint32_t PJ_FILE_MAGIC = 0x504A4A31; // "PJJ1"
        static constexpr uint16_t PJ_FILE_VERSION = 0x0001;
        // ====== æµå¼åºåˆ—化接口 ======
        void serialize(std::ostream& os) const;
        static bool deserialize(std::istream& is, CProcessJob& out, std::string* err = nullptr);
    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/SerializeUtil.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
#pragma once
#include <fstream>
// --------- ç§æœ‰/内部:读写工具 ---------
namespace SERVO {
    template<typename T>
    inline void write_pod(std::ostream& os, const T& v) {
        os.write(reinterpret_cast<const char*>(&v), sizeof(T));
    }
    template<typename T>
    inline bool read_pod(std::istream& is, T& v) {
        return bool(is.read(reinterpret_cast<char*>(&v), sizeof(T)));
    }
    inline void write_string(std::ostream& os, const std::string& s) {
        uint32_t n = static_cast<uint32_t>(s.size());
        write_pod(os, n);
        if (n) os.write(s.data(), n);
    }
    inline bool read_string(std::istream& is, std::string& s) {
        uint32_t n = 0; if (!read_pod(is, n)) return false;
        s.resize(n);
        if (n) return bool(is.read(&s[0], n));
        return true;
    }
    template<typename T>
    inline void write_vec(std::ostream& os, const std::vector<T>& v) {
        uint32_t n = static_cast<uint32_t>(v.size());
        write_pod(os, n);
        if (n) os.write(reinterpret_cast<const char*>(v.data()), sizeof(T) * n);
    }
    template<typename T>
    inline bool read_vec(std::istream& is, std::vector<T>& v) {
        uint32_t n = 0; if (!read_pod(is, n)) return false;
        v.resize(n);
        if (n) return bool(is.read(reinterpret_cast<char*>(v.data()), sizeof(T) * n));
        return true;
    }
    // vector<string> ç‰¹åŒ–写读
    inline void write_vec_str(std::ostream& os, const std::vector<std::string>& v) {
        uint32_t n = static_cast<uint32_t>(v.size());
        write_pod(os, n);
        for (const auto& s : v) write_string(os, s);
    }
    inline bool read_vec_str(std::istream& is, std::vector<std::string>& v) {
        uint32_t n = 0; if (!read_pod(is, n)) return false;
        v.clear(); v.reserve(n);
        for (uint32_t i = 0; i < n; ++i) { std::string s; if (!read_string(is, s)) return false; v.emplace_back(std::move(s)); }
        return true;
    }
    // optional<time_point> â†’ bool + int64 (ms since epoch)
    inline void write_opt_time(std::ostream& os, const std::optional<std::chrono::system_clock::time_point>& tp) {
        uint8_t has = tp.has_value() ? 1 : 0;
        write_pod(os, has);
        if (has) {
            auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tp->time_since_epoch()).count();
            int64_t v = static_cast<int64_t>(ms);
            write_pod(os, v);
        }
    }
    inline bool read_opt_time(std::istream& is, std::optional<std::chrono::system_clock::time_point>& tp) {
        uint8_t has = 0; if (!read_pod(is, has)) return false;
        if (!has) { tp.reset(); return true; }
        int64_t v = 0; if (!read_pod(is, v)) return false;
        tp = std::chrono::system_clock::time_point(std::chrono::milliseconds(v));
        return true;
    }
}
SourceCode/Bond/Servo/Servo.rc
Binary files differ
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>
@@ -200,9 +201,12 @@
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="CBaseDlg.h" />
    <ClInclude Include="CControlJob.h" />
    <ClInclude Include="CControlJobDlg.h" />
    <ClInclude Include="CCustomCheckBox.h" />
    <ClInclude Include="CCollectionEvent.h" />
    <ClInclude Include="CEquipmentPage3.h" />
    <ClInclude Include="CExpandableListCtrl.h" />
    <ClInclude Include="CGlassPool.h" />
    <ClInclude Include="ChangePasswordDlg.h" />
    <ClInclude Include="CMyStatusbar.h" />
@@ -324,12 +328,14 @@
    <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" />
    <ClInclude Include="Resource.h" />
    <ClInclude Include="SECSRuntimeManager.h" />
    <ClInclude Include="SecsTestDlg.h" />
    <ClInclude Include="SerializeUtil.h" />
    <ClInclude Include="Servo.h" />
    <ClInclude Include="ServoCommo.h" />
    <ClInclude Include="ServoDlg.h" />
@@ -349,9 +355,12 @@
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="CBaseDlg.cpp" />
    <ClCompile Include="CControlJob.cpp" />
    <ClCompile Include="CControlJobDlg.cpp" />
    <ClCompile Include="CCustomCheckBox.cpp" />
    <ClCompile Include="CCollectionEvent.cpp" />
    <ClCompile Include="CEquipmentPage3.cpp" />
    <ClCompile Include="CExpandableListCtrl.cpp" />
    <ClCompile Include="CGlassPool.cpp" />
    <ClCompile Include="ChangePasswordDlg.cpp" />
    <ClCompile Include="CMyStatusbar.cpp" />
@@ -470,6 +479,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,10 @@
    <ClCompile Include="CPageVarialbles.cpp" />
    <ClCompile Include="CPageReport.cpp" />
    <ClCompile Include="CPageCollectionEvent.cpp" />
    <ClCompile Include="ProcessJob.cpp" />
    <ClCompile Include="CControlJob.cpp" />
    <ClCompile Include="CExpandableListCtrl.cpp" />
    <ClCompile Include="CControlJobDlg.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -357,6 +361,11 @@
    <ClInclude Include="CPageVarialbles.h" />
    <ClInclude Include="CPageReport.h" />
    <ClInclude Include="CPageCollectionEvent.h" />
    <ClInclude Include="ProcessJob.h" />
    <ClInclude Include="CControlJob.h" />
    <ClInclude Include="SerializeUtil.h" />
    <ClInclude Include="CExpandableListCtrl.h" />
    <ClInclude Include="CControlJobDlg.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -26,6 +26,7 @@
#include "CPageVarialbles.h"
#include "CPageReport.h"
#include "CPageCollectionEvent.h"
#include "CControlJobDlg.h"
#ifdef _DEBUG
@@ -39,6 +40,8 @@
/* è¿è¡Œæ—¶é—´å®šæ—¶å™¨ */
#define TIMER_ID_UPDATE_RUMTIME            2
/* Test */
#define TIMER_ID_TEST                    3
// ç”¨äºŽåº”用程序“关于”菜单项的 CAboutDlg å¯¹è¯æ¡†
@@ -332,7 +335,7 @@
    // model init
    theApp.m_model.init();
    SetTimer(TIMER_ID_TEST, 1000, nullptr);
    // èœå•
    CMenu menu;
@@ -349,7 +352,8 @@
    HMENU hMenu = m_pTopToolbar->GetOperatorMenu();
    ASSERT(hMenu);
    ::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
    m_pTopToolbar->GetBtn(IDC_BUTTON_JOBS)->EnableWindow(TRUE);
    // Tab
    m_pPageGraph1 = new CPageGraph1();
@@ -875,6 +879,13 @@
        m_pMyStatusbar->setRunTimeText((LPTSTR)(LPCTSTR)strText);
    }
    else if(TIMER_ID_TEST == nIDEvent){
        static __int64 tttt = 0;
        tttt++;
        theApp.m_model.m_hsmsPassive.setVariableValue("CJobSpace", tttt % 10);
        theApp.m_model.m_hsmsPassive.setVariableValue("PJobSpace", tttt % 5);
    }
    CDialogEx::OnTimer(nIDEvent);
}
@@ -968,6 +979,11 @@
            m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
        }
    }
    else if (id == IDC_BUTTON_JOBS) {
        CControlJobDlg dlg;
        dlg.SetControlJob(theApp.m_model.m_master.getControlJob());
        dlg.DoModal();
    }
    else if (id == IDC_BUTTON_PORT_CONFIG) {
        CPortConfigurationDlg dlg;
        dlg.DoModal();
SourceCode/Bond/Servo/TopToolbar.cpp
@@ -29,6 +29,7 @@
    DDX_Control(pDX, IDC_BUTTON_RUN, m_btnRun);
    DDX_Control(pDX, IDC_BUTTON_RUN_CT, m_btnRunCt);
    DDX_Control(pDX, IDC_BUTTON_STOP, m_btnStop);
    DDX_Control(pDX, IDC_BUTTON_JOBS, m_btnCJobs);
    DDX_Control(pDX, IDC_BUTTON_ALARM, m_btnAlarm);
    DDX_Control(pDX, IDC_BUTTON_SETTINGS, m_btnSettings);
    DDX_Control(pDX, IDC_BUTTON_PORT_CONFIG, m_btnPortConfig);
@@ -56,6 +57,7 @@
    InitBtn(m_btnRunCt, "RunCt_High_32.ico", "RunCt_Gray_32.ico");
    InitBtn(m_btnStop, "Stop_High_32.ico", "Stop_Gray_32.ico");
    InitBtn(m_btnAlarm, "Alarm_o_32.ico", "Alarm_gray_32.ico");
    InitBtn(m_btnCJobs, "ControlJob_High_32.ico", "ControlJob_Gray_32.ico");
    InitBtn(m_btnSettings, "Settings_High_32.ico", "Settings_Gray_32.ico");
    InitBtn(m_btnRobot, "Robot_High_32.ico", "Robot_Gray_32.ico");
    InitBtn(m_btnPortConfig, "PortConfig_High_32.ico", "PortConfig_Gray_32.ico");
@@ -124,6 +126,11 @@
    x += 2;
    pItem = GetDlgItem(IDC_BUTTON_STOP);
    pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
    x += BTN_WIDTH;
    x += 2;
    pItem = GetDlgItem(IDC_BUTTON_JOBS);
    pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
    x += BTN_WIDTH;
    x += 2;
@@ -199,6 +206,7 @@
    case IDC_BUTTON_RUN:
    case IDC_BUTTON_RUN_CT:
    case IDC_BUTTON_STOP:
    case IDC_BUTTON_JOBS:
    case IDC_BUTTON_ALARM:
    case IDC_BUTTON_SETTINGS:
    case IDC_BUTTON_PORT_CONFIG:
SourceCode/Bond/Servo/TopToolbar.h
@@ -33,6 +33,7 @@
    CBlButton m_btnRun;
    CBlButton m_btnRunCt;
    CBlButton m_btnStop;
    CBlButton m_btnCJobs;
    CBlButton m_btnAlarm;
    CBlButton m_btnSettings;
    CBlButton m_btnPortConfig;
SourceCode/Bond/Servo/resource.h
Binary files differ
SourceCode/Bond/x64/Debug/CollectionEventList.txt
@@ -38,3 +38,4 @@
40000,E90_SPSM_NoState_To_NeedsProcessing,,(40000)
40001,E90_SPSM_InProcess_To_ProcessCompleted,,(40000)
50000,CarrierID_Readed,,(50000)
50001,PJ_Queued,,(50001)
SourceCode/Bond/x64/Debug/ReportList.txt
@@ -16,4 +16,5 @@
31000,(1,31000,31001)
40000,(1,10203,20000)
50000,(5000)
50001,(5003)
SourceCode/Bond/x64/Debug/Res/ControlJob_Gray_32.ico
SourceCode/Bond/x64/Debug/Res/ControlJob_High_32.ico
SourceCode/Bond/x64/Debug/VariableList.txt
@@ -8,7 +8,6 @@
701,PreviousProcessState,U1,
800,EFEMPPExecName,A20,
801,EQPPExecName,A20,
1000,CJobSpace,U1,
2000,RbRAxisTorque,I2,机器人R轴扭矩
2001,RbLAxisTorque,l2,机器人L轴扭矩
2002,RbZAxisTorque,l2,机器人Z轴扭矩
@@ -36,3 +35,6 @@
2024,CCDEnable,U2,"CCD使能:O:开启 1:屏蔽"
2025,FFUParameter,U2,FFU设定值
5000,CarrierID,A20,卡匣ID
5001,CJobSpace,U1,CJ Space
5002,PJobSpace,U1,PJ Space
5003,PJQueued,L,PJ Queued