From 8908f3613d5ca848249179d8c361576078f9781f Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期一, 18 八月 2025 17:12:10 +0800
Subject: [PATCH] 1.实现Create CJ功能和模拟测试;

---
 SourceCode/Bond/Servo/Servo.vcxproj              |    2 
 SourceCode/Bond/Servo/HsmsPassive.h              |    4 
 SourceCode/Bond/Servo/ProcessJob.h               |    2 
 SourceCode/Bond/Servo/ProcessJob.cpp             |    2 
 SourceCode/Bond/Servo/CControlJob.cpp            |  224 ++++++++++++++++++
 SourceCode/Bond/Servo/CControlJob.h              |  144 ++++++++++++
 SourceCode/Bond/EAPSimulator/CHsmsActive.h       |    3 
 SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h   |    1 
 SourceCode/Bond/Servo/HsmsPassive.cpp            |  147 +++++++++++
 SourceCode/Bond/EAPSimulator/EAPSimulator.rc     |    0 
 SourceCode/Bond/Servo/CMaster.h                  |    8 
 SourceCode/Bond/Servo/Model.cpp                  |   13 
 SourceCode/Bond/EAPSimulator/CHsmsActive.cpp     |   44 +++
 SourceCode/Bond/EAPSimulator/Resource.h          |    2 
 SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h     |    1 
 SourceCode/Bond/Servo/Servo.vcxproj.filters      |    2 
 SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp |    8 
 SourceCode/Bond/Servo/CMaster.cpp                |   61 ++++
 18 files changed, 653 insertions(+), 15 deletions(-)

diff --git a/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp b/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
index 7969e15..b334708 100644
--- a/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
+++ b/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;
diff --git a/SourceCode/Bond/EAPSimulator/CHsmsActive.h b/SourceCode/Bond/EAPSimulator/CHsmsActive.h
index 4ea6387..29abeb5 100644
--- a/SourceCode/Bond/EAPSimulator/CHsmsActive.h
+++ b/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);
 
diff --git a/SourceCode/Bond/EAPSimulator/EAPSimulator.rc b/SourceCode/Bond/EAPSimulator/EAPSimulator.rc
index 245a5d1..53eb764 100644
--- a/SourceCode/Bond/EAPSimulator/EAPSimulator.rc
+++ b/SourceCode/Bond/EAPSimulator/EAPSimulator.rc
Binary files differ
diff --git a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp b/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
index 13bb28c..e15ff68 100644
--- a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
+++ b/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);
+}
diff --git a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h b/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
index e289190..e10cf1a 100644
--- a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
+++ b/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();
 };
diff --git a/SourceCode/Bond/EAPSimulator/Resource.h b/SourceCode/Bond/EAPSimulator/Resource.h
index 6f261c8..96c191e 100644
--- a/SourceCode/Bond/EAPSimulator/Resource.h
+++ b/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
diff --git a/SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h b/SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h
index b3bfa28..09b9ed0 100644
--- a/SourceCode/Bond/HSMSSDK/Include/ISECS2Item.h
+++ b/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;
 };
diff --git a/SourceCode/Bond/Servo/CControlJob.cpp b/SourceCode/Bond/Servo/CControlJob.cpp
new file mode 100644
index 0000000..3f0237b
--- /dev/null
+++ b/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;
+            });
+    }
+}
diff --git a/SourceCode/Bond/Servo/CControlJob.h b/SourceCode/Bond/Servo/CControlJob.h
new file mode 100644
index 0000000..a9611a4
--- /dev/null
+++ b/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;
+    };
+}
+
diff --git a/SourceCode/Bond/Servo/CMaster.cpp b/SourceCode/Bond/Servo/CMaster.cpp
index ccd1f5d..81a0115 100644
--- a/SourceCode/Bond/Servo/CMaster.cpp
+++ b/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;
diff --git a/SourceCode/Bond/Servo/CMaster.h b/SourceCode/Bond/Servo/CMaster.h
index 6cb2965..d43642d 100644
--- a/SourceCode/Bond/Servo/CMaster.h
+++ b/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;
     };
 }
diff --git a/SourceCode/Bond/Servo/HsmsPassive.cpp b/SourceCode/Bond/Servo/HsmsPassive.cpp
index b71785e..689920b 100644
--- a/SourceCode/Bond/Servo/HsmsPassive.cpp
+++ b/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);
+
+
+
+	// 瑙i噴鏁版嵁锛屽緱鍒癈ontrolJob
+	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) {
+
+		// 绫籭d
+		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("鍙傛暟鎴栨姤鏂囦笉姝g‘", "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];
+
+		// 鍒涘缓绫籆ControlJob
+		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();
 
diff --git a/SourceCode/Bond/Servo/HsmsPassive.h b/SourceCode/Bond/Servo/HsmsPassive.h
index f229e93..0f5a636 100644
--- a/SourceCode/Bond/Servo/HsmsPassive.h
+++ b/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:
diff --git a/SourceCode/Bond/Servo/Model.cpp b/SourceCode/Bond/Servo/Model.cpp
index 71d4eef..6ce35c5 100644
--- a/SourceCode/Bond/Servo/Model.cpp
+++ b/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);
diff --git a/SourceCode/Bond/Servo/ProcessJob.cpp b/SourceCode/Bond/Servo/ProcessJob.cpp
index 2ca265a..16bfbb0 100644
--- a/SourceCode/Bond/Servo/ProcessJob.cpp
+++ b/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;
     }
diff --git a/SourceCode/Bond/Servo/ProcessJob.h b/SourceCode/Bond/Servo/ProcessJob.h
index 9cf2fc5..da5db9b 100644
--- a/SourceCode/Bond/Servo/ProcessJob.h
+++ b/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
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index 8246915..7bf97d3 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/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" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 230f6df..f96e572 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/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" />

--
Gitblit v1.9.3