LAPTOP-SNT8I5JK\Boounion
2025-08-18 8908f3613d5ca848249179d8c361576078f9781f
1.实现Create CJ功能和模拟测试;
已添加2个文件
已修改16个文件
668 ■■■■■ 文件已修改
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulator.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/Resource.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJob.cpp 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJob.h 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
@@ -385,6 +385,50 @@
    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
@@ -89,6 +89,9 @@
    // 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/EAPSimulator.rc
Binary files differ
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
@@ -94,6 +94,7 @@
    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()
@@ -286,6 +287,7 @@
    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()
@@ -414,3 +416,9 @@
    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
@@ -62,4 +62,5 @@
    afx_msg void OnBnClickedButtonQueryCjSpace();
    afx_msg void OnBnClickedButtonQueryPjSpace();
    afx_msg void OnBnClickedButtonCreatePj();
    afx_msg void OnBnClickedButtonCreateCj();
};
SourceCode/Bond/EAPSimulator/Resource.h
@@ -54,6 +54,8 @@
#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
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,224 @@
#include "stdafx.h"
#include "CControlJob.h"
#include <cctype>
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(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;
            });
    }
}
SourceCode/Bond/Servo/CControlJob.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
#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:
        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; }
        // â€”— 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(); }
        // â€”— æ ¡éªŒ â€”— //
        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);
    private:
        void markQueued();
        void markStart();
        void markEnd();
    private:
        // â€”— æ ‡è¯† & é…ç½® â€”— //
        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/CMaster.cpp
@@ -58,15 +58,21 @@
        m_bContinuousTransfer = false;
        m_nContinuousTransferCount = 0;
        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]);
@@ -1847,11 +1853,12 @@
        m_nContinuousTransferCount = round;
    }
    int CMaster::setProcessJobs(std::vector<SERVO::CProcessJob*>& pjs)
    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);
            }
        }
@@ -1860,11 +1867,61 @@
        return m_processJobs.size();
    }
    std::vector<SERVO::CProcessJob*>& CMaster::getProcessJobs()
    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);
        return 0;
    }
    CLoadPort* CMaster::getPortWithCarrierId(const std::string& carrierId) const
    {
        CLoadPort* pPort;
SourceCode/Bond/Servo/CMaster.h
@@ -14,6 +14,7 @@
#include "CCLinkIEControl.h"
#include "CRobotTask.h"
#include "ProcessJob.h"
#include "CControlJob.h"
#define CTStep_Unknow                   0
@@ -105,8 +106,10 @@
        int carrierRelease(unsigned int port);
        int getContinuousTransferCount();
        void setContinuousTransferCount(int round);
        int setProcessJobs(std::vector<SERVO::CProcessJob*>& pjs);
        std::vector<SERVO::CProcessJob*>& getProcessJobs();
        int setProcessJobs(std::vector<CProcessJob*>& pjs);
        std::vector<CProcessJob*>& getProcessJobs();
        CProcessJob* getProcessJob(const std::string& id);
        int setControlJob(CControlJob& controlJob);
        CLoadPort* getPortWithCarrierId(const std::string& carrierId) const;
    private:
@@ -191,6 +194,7 @@
    private:
        bool m_bEnableEventReport;
        bool m_bEnableAlarmReport;
        SERVO::CControlJob* m_pControlJob;
        std::vector<SERVO::CProcessJob*> m_processJobs;
    };
}
SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -632,6 +632,9 @@
        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);
        }
@@ -1538,6 +1541,140 @@
    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)
{
@@ -1611,7 +1748,7 @@
    ISECS2Item* pItemErrors = pMessage->getBody()->addItem();
    bool bHasError = false;
    for (auto p : pjs) {
        if (p->issue().empty()) {
        if (p->issues().empty()) {
            pItemPrjobIds->addItem(p->id().c_str(), "PRJOBID");
        }
        else {
@@ -1622,10 +1759,10 @@
        pItemErrors->addBoolItem(false, "ACKA");
        ISECS2Item* pItemErrors2 = pItemErrors->addItem();
        for (auto p : pjs) {
            if (!p->issue().empty()) {
            if (!p->issues().empty()) {
                ISECS2Item* pItemErr = pItemErrors2->addItem();
                pItemErr->addU4Item(p->issue()[0].code, "ERRCODE");
                pItemErr->addItem(("<" + p->id() + ">" + p->issue()[0].text).c_str(), "ERRTEXT");
                pItemErr->addU4Item(p->issues()[0].code, "ERRCODE");
                pItemErr->addItem(("<" + p->id() + ">" + p->issues()[0].text).c_str(), "ERRTEXT");
            }
        }
    }
@@ -1636,7 +1773,7 @@
    // é‡Šæ”¾æœ‰é—®é¢˜(未添加到master)的内存
    for (auto p : pjs) {
        if(!p->issue().empty()) delete p;
        if(!p->issues().empty()) delete p;
    }
    pjs.clear();
SourceCode/Bond/Servo/HsmsPassive.h
@@ -8,6 +8,7 @@
#include <set>
#include "CCollectionEvent.h"
#include "ProcessJob.h"
#include "CControlJob.h"
#define EQCONSTANT_VALUE_MAX    64
@@ -88,6 +89,7 @@
    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;
@@ -101,6 +103,7 @@
    QUERYPPIDLIST                onQueryPPIDList;
    CARRIERACTION                onCarrierAction;
    PRJOBMULTICREATE            onPRJobMultiCreate;
    CONTROLJOBCREATE            onControlJobCreate;
} SECSListener;
@@ -212,6 +215,7 @@
    int replyPurgeSpooledData(IMessage* pRecv);
    int replyQueryPPIDList(IMessage* pRecv);
    int replyTerminalDisplay(IMessage* pRecv);
    int replyCreateObj(IMessage* pRecv);
    int replyPRJobMultiCreate(IMessage* pRecv);
private:
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,
@@ -192,6 +192,11 @@
        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);
    m_hsmsPassive.setSoftRev((LPTSTR)(LPCTSTR)strSoftRev);
SourceCode/Bond/Servo/ProcessJob.cpp
@@ -53,7 +53,7 @@
        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
    }
    const std::vector<CProcessJob::ValidationIssue>& CProcessJob::issue()
    const std::vector<CProcessJob::ValidationIssue>& CProcessJob::issues()
    {
        return m_issues;
    }
SourceCode/Bond/Servo/ProcessJob.h
@@ -118,7 +118,7 @@
        };
        // è¿”回问题清单(空=通过)
        bool validate(const IResourceView& rv);
        const std::vector<ValidationIssue>& issue();
        const std::vector<ValidationIssue>& issues();
        // â€”— çŠ¶æ€æœºï¼ˆå¸¦å®ˆå«ï¼‰â€”â€”
        bool queue();           // NoState -> Queued
SourceCode/Bond/Servo/Servo.vcxproj
@@ -201,6 +201,7 @@
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="CBaseDlg.h" />
    <ClInclude Include="CControlJob.h" />
    <ClInclude Include="CCustomCheckBox.h" />
    <ClInclude Include="CCollectionEvent.h" />
    <ClInclude Include="CEquipmentPage3.h" />
@@ -351,6 +352,7 @@
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="CBaseDlg.cpp" />
    <ClCompile Include="CControlJob.cpp" />
    <ClCompile Include="CCustomCheckBox.cpp" />
    <ClCompile Include="CCollectionEvent.cpp" />
    <ClCompile Include="CEquipmentPage3.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -177,6 +177,7 @@
    <ClCompile Include="CPageReport.cpp" />
    <ClCompile Include="CPageCollectionEvent.cpp" />
    <ClCompile Include="ProcessJob.cpp" />
    <ClCompile Include="CControlJob.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -359,6 +360,7 @@
    <ClInclude Include="CPageReport.h" />
    <ClInclude Include="CPageCollectionEvent.h" />
    <ClInclude Include="ProcessJob.h" />
    <ClInclude Include="CControlJob.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />